Iteration protocols

Iteration protocols

ECMAScript 2015 的一些新增内容不是新的内置或语法,而是协议。这些协议可以通过任何符合某些约定的对象来实现。

有两种协议:可迭代协议和迭代器协议。

可迭代的协议

迭代协议允许 JavaScript 对象来定义或定制自己的迭代行为,比如什么值在挂绕for..of结构。一些内置类型是内置的迭代器,具有默认的迭代行为,比如Array或者Map其他类型(比如Object)不是。

为了可迭代,对象必须实现 @@ iterator 方法,这意味着对象(或其原型链中的一个对象)必须具有带@@迭代器键的属性,该键通过常量可用Symbol.iterator

PropertyValue
Symbol.iteratorA zero arguments function that returns an object, conforming to the iterator protocol.

每当一个对象需要被迭代时(比如在for..of循环的开始处),它的@@iterator方法被调用时没有参数,并且返回的迭代器被用来获取要被迭代的值。

迭代器协议

所述迭代器协议定义的标准方式产生的值(无论是有限的或无限的)的序列。

一个对象是一个迭代器,当它实现一个next()具有以下语义的方法时:

PropertyValue
nextA zero arguments function that returns an object with two properties: done (boolean) Has the value true if the iterator is past the end of the iterated sequence. In this case value optionally specifies the return value of the iterator. The return values are explained here. Has the value false if the iterator was able to produce the next value in the sequence. This is equivalent of not specifying the done property altogether. value - any JavaScript value returned by the iterator. Can be omitted when done is true. The next method always has to return an object with appropriate properties including done and value. If a non-object value gets returned (such as false or undefined), a TypeError ("iterator.next() returned a non-object value") will be thrown.

  • done (布尔值)

next方法始终必须返回具有适当属性(包括done和)的对象value。如果返回非对象值(例如falseor undefined),TypeError则会抛出(“iterator.next()返回非对象值”)。

一些迭代器依次迭代:

var someArray = [1, 5, 7]; var someArrayEntries = someArray.entries( someArrayEntries.toString(           // "[object Array Iterator]" someArrayEntries === someArrayEntries[Symbol.iterator]( // true

使用迭代协议的示例

String是一个内置可迭代对象的例子:

var someString = 'hi'; typeof someString[Symbol.iterator]; // "function"

String默认的迭代器会逐个返回字符串的代码点:

var iterator = someString[Symbol.iterator]( iterator + ''; // "[object String Iterator]" iterator.next( // { value: "h", done: false } iterator.next( // { value: "i", done: false } iterator.next( // { value: undefined, done: true }

一些内置的构造,例如 spread 运算符,使用相同的迭代协议:

[...someString] // ["h", "i"]

我们可以通过提供自己的代码来重新定义迭代行为 @@iterator

var someString = new String('hi' // need to construct a String object explicitly to avoid auto-boxing someString[Symbol.iterator] = function() { return { // this is the iterator object, returning a single element, the string "bye" next: function() { if (this._first) { this._first = false; return { value: 'bye', done: false }; } else { return { done: true }; } }, _first: true }; };

注意重新定义 @@iterator 如何影响使用迭代协议的内置构造的行为:

[...someString]; // ["bye"] someString + ''; // "hi"

可重用的例子

内置迭代器

StringArrayTypedArrayMapSet都内置 iterables,因为每个他们的原型对象实现的@@iterator方法。

用户定义的迭代器

我们可以像这样做我们自己的迭代器:

var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable]; // [1, 2, 3]

内置API接受迭代

有许多接受 iterables 的 API,例如:Map([iterable])WeakMap([iterable])Set([iterable])WeakSet([iterable])

var myObj = {}; new Map([[1, 'a'], [2, 'b'], [3, 'c']]).get(2 // "b" new WeakMap([[{}, 'a'], [myObj, 'b'], [{}, 'c']]).get(myObj // "b" new Set([1, 2, 3]).has(3 // true new Set('123').has('2' // true new WeakSet(function* () { yield {}; yield myObj; yield {}; }()).has(myObj // true

另见 Promise.all(iterable)Promise.race(iterable)Array.from()

需要迭代的语法

一些语句和表达式期望迭代,例如for-of循环,扩展运算符yield*和解构赋值:

for(let value of ['a', 'b', 'c']){ console.log(value } // "a" // "b" // "c" [...'abc']; // ["a", "b", "c"] function* gen() { yield* ['a', 'b', 'c']; } gen().next( // { value:"a", done:false } [a, b, c] = new Set(['a', 'b', 'c'] a // "a"

非格式化的迭代

如果一个迭代器的@@iterator方法没有返回一个迭代器对象,那么它是一个不完整的迭代器。如此使用它可能会导致运行时异常或错误行为:

var nonWellFormedIterable = {} nonWellFormedIterable[Symbol.iterator] = () => 1 [...nonWellFormedIterable] // TypeError: [] is not a function

迭代器的例子

简单的迭代器

function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {done: true}; } }; } var it = makeIterator(['yo', 'ya'] console.log(it.next().value // 'yo' console.log(it.next().value // 'ya' console.log(it.next().done // true

无限迭代器

function idMaker() { var index = 0; return { next: function(){ return {value: index++, done: false}; } }; } var it = idMaker( console.log(it.next().value // '0' console.log(it.next().value // '1' console.log(it.next().value // '2' // ...

带生成器

function* makeSimpleGenerator(array) { var nextIndex = 0; while (nextIndex < array.length) { yield array[nextIndex++]; } } var gen = makeSimpleGenerator(['yo', 'ya'] console.log(gen.next().value // 'yo' console.log(gen.next().value // 'ya' console.log(gen.next().done // true function* idMaker() { var index = 0; while (true) yield index++; } var gen = idMaker( console.log(gen.next().value // '0' console.log(gen.next().value // '1' console.log(gen.next().value // '2' // ...

使用 ECMAScript 6 类

class SimpleClass { constructor(data) { this.index = 0; this.data = data; } [Symbol.iterator]() { return { next: () => { if (this.index < this.data.length) { return {value: this.data[this.index++], done: false}; } else { this.index = 0; //If we would like to iterate over this again without forcing manual update of the index return {done: true}; } } } }; } const simple = new SimpleClass([1,2,3,4,5] for (const val of simple) { console.log(val //'0' '1' '2' '3' '4' '5' }

生成器对象是一个迭代器还是可迭代的?

生成器对象既是迭代器又是可迭代的:

var aGeneratorObject = function* () { yield 1; yield 2; yield 3; }( typeof aGeneratorObject.next; // "function", because it has a next method, so it's an iterator typeof aGeneratorObject[Symbol.iterator]; // "function", because it has an @@iterator method, so it's an iterable aGeneratorObject[Symbol.iterator]() === aGeneratorObject; // true, because its @@iterator method returns itself (an iterator), so it's an well-formed iterable [...aGeneratorObject]; // [1, 2, 3]

浏览器兼容性

FeatureChromeFirefox (Gecko)Internet ExplorerOperaSafari (WebKit)
Basic support39.027.0 (27.0)No support2610
IteratorResult object instead of throwing(Yes)29.0 (29.0)No support(Yes)10

FeatureAndroidAndroid WebviewFirefox Mobile (Gecko)IE MobileOpera MobileSafari MobileChrome for Android
Basic supportNo support(Yes)27.0 (27.0)No supportNo support1039.0
IteratorResult object instead of throwingNo support?29.0 (29.0)No supportNo support?(Yes)

Firefox特定的笔记

IteratorResult 对象返回而不是抛出

从 Gecko 29(Firefox 29 / Thunderbird 29 / SeaMonkey 2.26)开始,完整的生成器函数不再抛出TypeError“生成器已经完成”。相反,它会返回一个IteratorResult对象,如{ value: undefined, done: true }(错误958951)。

Iterator属性和@@iterator符号

从 Gecko 17(Firefox 17 / Thunderbird 17 / SeaMonkey 2.14)到 Gecko 26(Firefox 26 / Thunderbird 26 / SeaMonkey 2.23 / Firefox OS 1.2)使用该iterator属性(bug 907077),并且从 Gecko 27到 Gecko 35 使用了"@@iterator"占位符。在 Gecko 36(Firefox 36 / Thunderbird 36 / SeaMonkey 2.33)中,@@iterator实现了该符号(错误918828)。

规范

SpecificationStatusComment
ECMAScript 2015 (6th Edition, ECMA-262)The definition of 'Iteration' in that specification.StandardInitial definition.
ECMAScript Latest Draft (ECMA-262)The definition of 'Iteration' in that specification.Living Standard