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])); // trueconsole.log(isIterator("Hello")); // trueconsole.log(isIterator(new Map())); // trueconsole.log(isIterator(new Set())); // trueconsole.log(isIterator(new WeakMap())); // falseconsole.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 说明