ECMAScript 2015的几个补充,并不是新的内置或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。
有两个协议:可迭代协议和迭代器协议。
可迭代协议
可迭代协议允许 JavaScript 对象去定义或定制它们的迭代行为, 例如(定义)在一个 for..of
结构中什么值可以被循环(得到)。一些内置类型都是内置的可遍历对象并且有默认的迭代行为, 比如 Array
or Map
, 另一些类型则不是 (比如Object
) 。
为了变成可遍历对象, 一个对象必须实现 @@iterator 方法, 意思是这个对象(或者它原型链prototype chain上的某个对象)必须有一个名字是 Symbol
.iterator
的属性:
属性 | 值 |
---|---|
[Symbol.iterator] |
返回一个对象的无参函数,被返回对象符合迭代器协议。 |
当一个对象需要被遍历的时候(比如开始用于一个for..of循环中
),它的@@iterator方法被调用并且无参数,然后返回一个用于在遍历中获得值的迭代器。
迭代器协议
该迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值。
当一个对象被认为是一个迭代器时,它实现了一个 next()
的方法并且拥有以下含义:
属性 | 值 |
---|---|
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 operator,内部也使用了同样的迭代协议:
[...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"
可迭代对象示例
内置可迭代对象
String
, Array
, TypedArray
, Map
and Set
是所有内置可迭代对象, 因为它们的原型对象都有一个 @@
iterator
方法.
自定义可迭代对象
我们可以实现一个自己的可迭代对象,就像这样:
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable]; // [1, 2, 3]
接受可迭代对象的内置 APIs
许多 APIs 接受可迭代对象(作为参数,译注), 例如:Map([iterable])
, WeakMap([iterable])
, Set([iterable])
and 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
循环,spread operator, yield* 和
destructuring assignment。
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"
Non-well-formed (非-良好-格式化的)可迭代对象
如果一个可迭代对象的 @@iterator 方法不是返回一个迭代器对象,那么它就是一个
non-well-formed 可迭代对象 。使用它可能会发生如下的运行时异常或者 buggy 行为:
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' // ...
生成器对象到底是一个迭代器还是一个可迭代对象?
生成器对象 既是迭代器也是可迭代对象:
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 return its self (an iterator), so it's an well-formed iterable [...aGeneratorObject]; // [1, 2, 3]
浏览器兼容性
特性 | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari (WebKit) |
---|---|---|---|---|---|
基本支持 | 39.0 | 27.0 (27.0) | 未实现 | 26 | 未实现 |
IteratorResult object instead of throwing |
(Yes) | 29.0 (29.0) | 未实现 | (Yes) | 未实现 |
特性 | Android | Android Webview | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile | Chrome for Android |
---|---|---|---|---|---|---|---|
基本支持 | 未实现 | (Yes) | 27.0 (27.0) | 未实现 | 未实现 | 未实现 | 39.0 |
IteratorResult object instead of throwing |
未实现 | ? | 29.0 (29.0) | 未实现 | 未实现 | 未实现 | (Yes) |
Firefox-specific 批注
IteratorResult
用对象返回代替错误抛出
从Gecko 29 (Firefox 29 / Thunderbird 29 / SeaMonkey 2.26)开始, 构造器不再抛出 TypeError
"generator has already finished". 取而代之的是它返回一个IteratorResult
对象类似于 { value: undefined, done: true }
(bug 958951).
Iterator
property and @@iterator
symbol
From Gecko 17 (Firefox 17 / Thunderbird 17 / SeaMonkey 2.14) to Gecko 26 (Firefox 26 / Thunderbird 26 / SeaMonkey 2.23 / Firefox OS 1.2) the iterator
property was used (bug 907077), and from Gecko 27 to Gecko 35 the "@@iterator"
placeholder was used. In Gecko 36 (Firefox 36 / Thunderbird 36 / SeaMonkey 2.33), the @@iterator
symbol got implemented (bug 918828).
规范
规范 | 状态 | 备注 |
---|---|---|
ECMAScript 2015 (6th Edition, ECMA-262) Iteration |
Standard |
Initial definition. |
ECMAScript Latest Draft (ECMA-262) Iteration |
Living Standard | ES7(ES2016/ES2017) |
参考
- 更多有关 ES2015 生成器的信息,请参考 the function*() documentation.