乐趣区

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 说明

退出移动版