ES6-之-IteratorGenerator-一

Iterator由来

不推荐Iterator方法。 Iterator 函数是一个 SpiderMonkey 专有特性,并且会在某一时刻被删除。
有一点,需要清楚的,就是“迭代协议”。迭代协议MDN说明

// 简单示例,摘自“深入理解ES6”
function createIterator(items) {
    let i = 0;
    
    return {
        next: function() {
            let done = (i >= items.length);
            let value = !done ? items[i++] : undefined;
            
            return {
                done,
                value
            }
        }
    }
}
let iterator = createIterator([1, 2, 3]);

console.log(iterator.next()); // { done: false, value: 1 }
console.log(iterator.next()); // { done: false, value: 2 }
console.log(iterator.next()); // { done: false, value: 3 }
console.log(iterator.next()); // { done: true, value: undefined }
// 之后所有的调用都会返回相同的内容
console.log(iterator.next()); // { done: true, value: undefined }

Generator定义

生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字yield。星号可以紧挨着function关键字,也可以在中间加一个空格。

function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}
let iterator = createIterator();
console.log(iterator.next());  // { value: 1, done: false }
console.log(iterator.next());  // { value: 2, done: false }
console.log(iterator.next());  // { value: 3, done: false }

console.log(iterator.next());  // { value: undefined, done: true }

// 换个方法
function *createIterator(items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
}

let iterator = createIterator([1, 2, 3]);
// iterator 与前面代码创建的 iterator 功能一样,可以试一下。

yield的使用限制
yield关键字只能在生成器内部使用,在其他地方使用会导致程序抛出语法错误,即便在生成器的函数里使用也如此。

function *createIterator(items) {
    items.forEach(function(item) {
        yield item + 1;
    });
}
// 会报语法错误 node ./iterator.js

从字面理解,yield关键字确定在createIterator()函数内部,但是它与return关键字一样,二者都不能穿透函数边界。嵌套函数中的return语句不能用作函数的返回语句,而此处嵌套函数中的yield语句会导致程序抛出语法错误。
生成器函数表达式&对象方法
通过上面的方法,关于函数表达式和对象方法,直接上代码吧,更明白。

// 函数表达式
let createIterator = function *(items) {
    for (let i =0; i < items.length; i++) {
        yield items[i];
    }
}

let iterator = createIterator([1, 2, 3]);

// 对象方法
let o = {
    createIterator: function *(items) {
        yield items[i];
    }
}

let iterator = o.createIterator([1, 2, 3]);

可迭代对象 & for-of 循环

看过Symbol文章的小伙伴应该都知道,Symbol.iterator就是 well-known Symbol之一。可迭代对象就具有Symbol.iterator属性,它是一种与迭代器密切相关的对象。它通过指定的函数可以返回一个作用于附属对象的迭代器。在ES6中,所有的集合对象(Array, Set, Map)和字符串都是可迭代对象,这些对象中都有默认的迭代器。当然,ES中也添加了for-of循环这些可迭代对象。

  • 迭代器
  • for-of循环

这是解决循环内部索引跟踪问题的关键工具。
for-of循环每执行一次都会调用可迭代对象中的next()方法,并将迭代器返回的结果对象的value属性存储在一个变量中,循环将持续执行这一过程直到返回对象的done属性为true。

let values = [1, 2, 3];
for (let num of values) {
    console.log(num);
}
// 输出:
// 1
// 2
// 3

示例说明:
for-of循环的代码通过调用values数组的Symbol.iterator方法来获取迭代器,这一过程是在Javascript引擎背后完成的。随后迭代器的next()的方法被多次调用,从其返回对象的value属性读取值并存储在变量num中,直到对象的done为true时循环退出,所以num不会赋值为undefined

访问默认迭代器

从上面的例子可以看出,可迭代对象都有一个默认迭代器。这个迭代器可通过Symbol.iterator来访问。

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next());  // { value: 1, done: false }
console.log(iterator.next());  // { value: 2, done: false }
console.log(iterator.next());  // { value: 3, done: false }
console.log(iterator.next());  // { value: undefined, done: true }

由此,我们可以判断对象是否可迭代,是不是有更好的方法?

function isIterator(object) {
    return typeof object[Symbol.iterator] === "function";
}

console.log(isIterator([1, 2, 3]));  // true
console.log(isIterator("Hello"));  // true
console.log(isIterator(new Map()));  // true
console.log(isIterator(new Set()));  // true
console.log(isIterator(new WeakMap()));  // false
console.log(isIterator(new WeakSet()));  // false

创建可迭代对象

默认情况下,开发者定义的对象都是不可迭代的对象,但如果给Symbol.iterator属性添加一个生成器,则可以将其变为可迭代对象。

let collection = {
    items: [],
    *[Symbol.iterator]() {
        for (let item of this.items) {
            yield item;
        }
    }
};

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for (let item of collection){
    console.log(item);
}
// 输出:
// 1
// 2
// 3

…未完待续…
Iterator&Generator 之 MDN 说明

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理