Skip to content

generators and iterators

garevna edited this page Jan 20, 2019 · 31 revisions

🎓 Генераторы и итераторы

ES6

Генератор

Функция-генератор объявляется с помощью ключевого слова function*

⚠️ * - обязательный атрибут функции-генератора

Функция-генератор определяет порядок ( протокол ) итерирования структуры данных

Сама по себе функция-генератор ничего не итерирует

С помощью функции-генератора создается объект-итератор

let iterator = generator ( ... )

Она ничего не возвращает, кроме, как уже было сказано, объекта-итератора с заложенным в нем протоколом перебора значений

⚠️ Именно поэтому генератор вместо оператора return использует оператор yield

function* generator ( ... ) {
    ...
    yield ...
}

Оператор yield позволяет управлять работой итератора

Этим оператором функция-генератор говорит итератору, что в этом месте нужно остановиться и вернуть текущее значение


next()

У объекта-итератора есть обязательный метод next()

С помощью этого метода итератор переходит от текущего элемента структуры данных к следующему

⚠️ Этот метод возвращает объект с двумя свойствами: value и done

  • Свойство value содержит то, что указано в протоколе генератора после ключевого слова слова yield
  • Свойство done принимает значение true, когда процесс итерирования структуры данных завершен

☕ 1️⃣

function* colorsGenerator () {
    while ( true ) {
        yield `rgb(
            ${Math.round ( Math.random() * 255 )},
            ${Math.round ( Math.random() * 255 )},
            ${Math.round ( Math.random() * 255 )}
        )`
    }
}

let colorIterator = colorsGenerator ()

for ( var x=0; x < 100; x++ ) {
    let point = document.body.appendChild (
        document.createElement ( 'div' )
    )
    point.style = `
        float: left;
        width: 10px;
        height: 10px;
        background-color: ${ colorIterator.next().value};
    `
}

Используя IIFE, можно сократь код:

const colorIterator = ( function* () {
    while ( true ) {
        yield `rgb(
            ${Math.round ( Math.random() * 255 )},
            ${Math.round ( Math.random() * 255 )},
            ${Math.round ( Math.random() * 255 )}
        )`
    }
})()

Также можно отдельно вынести в функцию код создания элемента с параметрами ширины и высоты:

function createColoredElement ( w, h ) {
    let point = document.createElement ( 'div' )
    point.style = `
        position: absolute;
        width: ${w}px;
        height: ${h}px;
        background-color: ${ colorIterator.next().value};
    `
    return point
}

После чего можно в цикле создавать элементы:

for ( var x = 0; x < 75; x++ ) {
    document.body.appendChild (
        createColoredElement ( 400-x*5, 400-x*5 )
    )
}

☕ 2️⃣

Пусть есть некий объект user

let user = {
    login: "Сергей",
    avatar: "https://www.shareicon.net/data/2015/12/14/207817_face_300x300.png",
    email: "serg789@gmail.com",
    place ( tagName ) {
        return document.body.appendChild (
            document.createElement ( tagName )
        )
    },
    showAvatar () {
        let ava = this.place ( "img" )
        ava.src = this.avatar
        ava.width = "70"
        return ava
    },
    showLogin () {
        let x = this.place ( "h3" )
            .innerHTML = this.login
        return x
    },
    showEmail () {
        let x = this.place ( "p" )
            .innerHTML = this.email
        return x
    }
}

С помощью генератора определим протокол итерирования этого объекта:

user.generator = function* () {
    yield this.showLogin ()
    yield this.showEmail ()
    yield this.showAvatar ()
}

Теперь создадим объект итератора:

user.iterator = user.generator ()

и запустим цикл итерирования:

while ( !user.iterator.next().done ) {}

На самом деле такое решение является чрезмерно громоздким

Все значительно упростится с использованием глобального символа Symbol.iterator


Symbol.iterator

Все очень просто:

Если у объекта есть свойство Symbol.iterator, то этот объект является итерабельным

( то есть можно перебирать его свойства оператором for...of )

Symbol.iterator является ссылкой на функцию-генератор


Используем Symbol.iterator в контексте предыдущего примера

user [ Symbol.iterator ] = function* () {
    yield this.showLogin ()
    yield this.showEmail ()
    yield this.showAvatar ()
}

Теперь объект user можно итерировать обычным for...of

for ( var x of user ) {}

или воспользоваться оператором spread:

console.log ( ...user )

☕ 3️⃣

function* someGenerator ( startValue, endValue ) {
    let y = startValue
    while ( y < endValue ) 
        yield y += String.fromCharCode ( y.charCodeAt( y.length-1 ) + 1 )
}
const someIterator = someGenerator ( "a", "abcdef" )

☕ 4️⃣

Асинхронный генератор

Создадим генератор, который выдает по одному символу в секунду из массива, переданного ему в качестве аргумента

async function* messageGenerator ( arr ) {
    while ( arr.length > 0 ) {
        let result = await new Promise (
            function ( resolve ) {
                setTimeout (
                    () => resolve ( arr.shift() ),
                    1000
                )
            }
        )
        yield result
    }
}

Поскольку протокол итерирования, заложенный в генераторе, возвращает промис на каждой итерации, для работы с ним объявим асинхронную функцию showMessage

showMessage создаст итератор с помощью генератора messageGenerator, передав ему строку, которая будет выводиться на страницу по одному символу в секунду

showMessage будет ждать ( await ), когда асинхронный итератор вернет очередное значение, и после этого выведет его на страницу

async function showMessage ( message ) {
    const iterator = messageGenerator ( [...message] )
    let finish = false

    while ( !finish ) {
        let currentState = await iterator.next()
        document.body.innerText += !currentState.done ?
            currentState.value : ""
        finish = currentState.done
    }
}

Вызовем асинхронную функцию showMessage:

showMessage ( "Привет, студент!" )

☕ 5️⃣

Связные списки

Пусть у нас есть массив объектов

const objects = [
    { val: "first",  nextItem: "second" },
    { val: "forth",  nextItem: "fifth" },
    { val: "sixth",  nextItem: null },
    { val: "third",  nextItem: "forth" },
    { val: "fifth",  nextItem: "sixth" },
    { val: "second", nextItem: "third" }
]

Каждый элемент массива содержит свойство nextItem - ссылку на другой элемент этого же массива

Создадим протокол итерирования такого массива

Пусть элементы массива перебираются не в том порядке, в котором они расположены в массиве, а по новому протоколу, т.е. следующим будет выбираться элемент, указанный в свойстве nextItem текущего элемента

function* someGenerator ( objs ) {
    let currentItem = objs [ 0 ]
    let nextItem = objs [ 0 ]
    while ( !!nextItem ) {
        currentItem = nextItem
        nextItem = !!currentItem.nextItem ? 
            objs.find ( x => currentItem.nextItem === x.val )
            : null
        yield currentItem.val
    }
}

Генератор принимает в качестве аргумента ссылку на итерируемый массив

Создадим итератор для массива objects

var iterator = someGenerator ( objects )

Теперь можно использовать метод next() итератора iterator


📌 Изменим протокол итерирования массива

objects[Symbol.iterator] = function* () {
    let currentItem = this [ 0 ]
    let nextItem = this [ 0 ]
    while ( !!nextItem ) {
        currentItem = nextItem
        nextItem = !!currentItem.nextItem ? 
            this.find ( x => currentItem.nextItem === x.val )
            : null
        yield currentItem.val
    }
}

Теперь оператор for...of будет итерировать массив objects в нужном порядке

for ( let obj of objects )
    console.log ( obj )

Кроме того, при деструктуризации массива objects значения будут возвращены в указанном протоколом порядке

let [ a, b, c, d ] = objects

☕ 6️⃣

const elements = [
    { tagName: "div", attrs: { id: "first", innerText: "first" } },
    { tagName: "article", attrs: { id: "second", innerText: "second" } },
    { tagName: "figure", attrs: { id: "third", innerText: "third" } },
    { tagName: "p", attrs: { id: "forth", innerText: "forth" } }
]

elements [ Symbol.iterator ] = function* () {
    let itemNum = 0
    while ( itemNum < this.length ) {
        yield ( () => {
            var elem = document.body.appendChild ( 
                document.createElement (
                    this [ itemNum ].tagName
                )
            )
            if ( this [ itemNum ].attrs ) 
                for ( var x in this [ itemNum ].attrs ) {
                    elem [ x ] = this [ itemNum ].attrs [ x ]
                }
            itemNum++
            return elem
        })()
    }
}

for ( let elem of elements ) {}

☕ 7️⃣

let btn = document.body.appendChild (
    document.createElement ( "button" )
)
btn.innerText = "new"
btn.onclick = function ( event ) {
    let ava = getAvatar.next()
    if ( !ava.done ) document.body.appendChild ( ava.value )
}

function* avaGenerator () {
    let num = 9
    while ( ++num < 99 ) {
        let ava = document.createElement ( "img" )
        ava.src = `https://www.shareicon.net/data/2015/12/14/2078${num}_face_300x300.png`
        ava.width = "80"
        yield ava
    }
}

let getAvatar = avaGenerator ()

© Irina H.Fylyppova 2018
Использование данных материалов или любой их части коммерческими школами ( курсами ) является нарушением авторских прав


Новая версия


1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19

Занятие 1

⤵️

Занятие 2

⤴️ ⤵️

Занятие 3

⤴️ ⤵️

Занятие 4

⤴️ ⤵️

Занятие 5

⤴️ ⤵️

Занятие 6

⤴️ ⤵️

Занятие 7

⤴️ ⤵️

Занятие 8

⤴️ ⤵️

Занятие 9

⤴️ ⤵️

Занятие 10

⤴️ ⤵️

Занятие 11

⤴️ ⤵️

Занятие 12

⤴️ ⤵️

Занятие 13

⤴️ ⤵️

Занятие 14

⤴️ ⤵️

Занятие 15

⤴️ ⤵️

Занятие 16

⤴️ ⤵️

Занятие 17

⤴️ ⤵️

Занятие 18

⤴️ ⤵️

Занятие 19

⤴️ ⤵️

⤴️

ico20 Дополнительно
dir-20 Справочная инфо

Clone this wiki locally