灏天阁

图解Js生成器和迭代器

· Yin灏

下面使用 [...] 语法,将生成的值扩展到一个数组中去

function * getEmojis() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}

const genObj = getEmojis();
console.log([...genObj]); // [1, 2, 3, 4]

或者使用 for…of 循环

function* getEmojis() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}

const genObj = getEmojis();

for (let item of genObj) {
    console.log(item); // 1 2 3 4
}

测试迭代器属性:

const array = ['1', '2', '3']; // 数组

const string = 'I love JavaScript!'; // 字符串

const object = { name: 'Lydia Hallie' }; // 对象

function regularFunction() { // 函数
    return 'I am a basic function.';
}

function * generatorFunction() { // 生成器函数
    return 'I am a generator function';
}

const generatorObject = generatorFunction();

console.log(array[Symbol.iterator]); // ƒ values() { [native code] }
console.log(string[Symbol.iterator]); // ƒ [Symbol.iterator]() { [native code] }
console.log(generatorObject[Symbol.iterator]); // ƒ [Symbol.iterator]() { [native code] }
console.log(object[Symbol.iterator]); // undefined

[Symbol.iterator] 必须返回一个迭代器,该迭代器包含一个 next() 方法,该方法返回一个对象 { value: '...', done: false/true }

下面让 object 变得可迭代:

const object = { name: 'Lydia Hallie' };

object[Symbol.iterator] = function *() {
    yield this;
} 

console.log(object[Symbol.iterator]);

console.log([...object])

for(let item of object) {
    console.log(item)
}

// 获取对象的键值
object[Symbol.iterator] = function *() {
    yield Object.keys(this);
} 

搞定!Object.keys(this) 是个数组,所以生成的值也是一个数组。然后我们把这个生成的数组扩展到另一个数组中,结果就得到一个嵌套的数组。我们并不想要这个,只是想生成每个单独的键!

我们可以用 yield* 来做到这一点。然后我们委托给另一个生成器!

const emojis = ['😊', '😔', '😟'];

function* genFunc() {
    yield '☀';
    yield * emojis;
    yield '🚀';
}

const genObj = genFunc();

console.log([...genObj]); // ['☀', '😊', '😔', '😟', '🚀']

在继续迭代 genObj 迭代器之前,被委托的生成器的每个值都生成了。

生成器函数的另一种用法,是我们可以(在某种程度上)将它们用作为观察者函数。生成器可以等待传入的数据,并且只有当该数据被传递过来时,才会处理它。比如:

function * generatorFunction() {
    const second = yield 'First!';
    console.log(second);
    return 'All done';
}

这里最大的区别是我们不是只有前一个例子中所看到的 yield [value],而是将其赋值给一个称为 second 的值,并生成值字符串 First!。这是第一次调用 next()方法时会生成的值。

首先遇到第一行上的 yield,并生成值 First!。那么,变量 second 的值是什么呢?

变量second的值实际上是下次我们调用 next() 方法时传递给该方法的值!这一次,我们给next()方法传一个字符串'I like JavaScript'

生成器的最大优点之一就是它们是懒求值的。也就是说,在调用next()方法后返回的值仅在我们明确要求后才计算!普通函数没有这种功能:所有值都为我们生成了,以防将来某个时间需要用到它。

案例
const bookClubs = [{
    name: 'The Cool Club',
    clubMembers: [{
        name: 'John Doe',
        books: [{
            id: 'hs891',
            title: 'A Perfect'
        }, {
            id: 'ey812',
            title: 'A Good Book'
        }]
    }]
}, {
    name: 'The Better Club',
    clubMembers: [{
        name: 'John Doe',
        books: [{
            id: 'u8291',
            title: 'A Greet Book'
        }, {
            id: 'p9201',
            title: 'A Nice Book'
        }]
    }]
}];

// console.log(bookClubs);

function* iterateBooks(books) {
    for (let i = 0; i < books.length; i++) {
        yield books[i];
    }
}

function * iterateMember(members) {
    for(let i = 0; i < members.length; i++) {
        const clubMember = members[i];
        yield * iterateBooks(clubMember.books)
    }
}

function * iterateBookClubs(bookClubs) {
    for(let i = 0; i < bookClubs.length; i++) {
        const bookClub = bookClubs[i];
        yield * iterateMember(bookClub.clubMembers);
    }
}

// const it = iterateBookClubs(bookClubs);

// console.log(it.next())
// console.log(it.next())
// console.log(it.next())
// console.log(it.next())
// console.log(it.next())

function findBook(id) {
    const genObj = iterateBookClubs(bookClubs);
    let result = genObj.next();
    while(!result.done) {
        if(result.value.id === id) {
            return result.value;
        } else {
            result = genObj.next();
        }
    }
}

console.log(findBook('p9201')); // {id: 'p9201', title: 'A Nice Book'}

- Book Lists -