迭代协议

 

ECMAScript 2015的几个补充,并不是新的内置或语法,而是协议这些协议可以被任何遵循某些约定的对象实现。

有两个协议:可迭代协议迭代器协议

可迭代协议

可迭代协议允许 JavaScript 对象去定义或定制它们的迭代行为, 例如(定义)在一个 for..of 结构中什么值可以被循环(得到)。一些内置类型都是内置的可遍历对象并且有默认的迭代行为, 比如 Array or Map, 另一些类型则不是 (比如Object) 。

为了变成可遍历对象, 一个对象必须实现 @@iterator 方法, 意思是这个对象(或者它原型链prototype chain上的某个对象)必须有一个名字是 Symbol.iterator 的属性:

属性
[Symbol.iterator] 返回一个对象的无参函数,被返回对象符合迭代器协议。

当一个对象需要被遍历的时候(比如开始用于一个for..of循环中),它的@@iterator方法被调用并且无参数,然后返回一个用于在遍历中获得值的迭代器

迭代器协议

迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值。

当一个对象被认为是一个迭代器时,它实现了一个 next() 的方法并且拥有以下含义:

属性
next

返回一个对象的无参函数,被返回对象拥有两个属性:

  • done (boolean)
    • 如果迭代器已经经过了被迭代序列时为 true。这时 value 可能描述了该迭代器的返回值。返回值在这里有更多解释。
    • 如果迭代器可以产生序列中的下一个值,则为 false。这等效于连同 done 属性也不指定。
  • value - 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。

一些迭代器是转换自可迭代对象:

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)

参考

文档标签和贡献者