非常教程

JavaScript参考手册

其他 | Miscellaneous

Iteration protocols

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

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

可迭代的协议

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

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

Property

Value

Symbol.iterator

A zero arguments function that returns an object, conforming to the iterator protocol.

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

迭代器协议

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

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

Property

Value

next

A 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 (布尔值)
    • true如果迭代器已经超过了迭代序列的末尾,则具有该值。在这种情况下,value可以选择指定迭代器的返回值。返回值在这里解释。
    • false如果迭代器能够生成序列中的下一个值,则具有该值。这相当于没有done完全指定属性。
  • value - 迭代器返回的任何JavaScript值。当可以省略donetrue

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]

浏览器兼容性

Feature

Chrome

Firefox (Gecko)

Internet Explorer

Opera

Safari (WebKit)

Basic support

39.0

27.0 (27.0)

No support

26

10

IteratorResult object instead of throwing

(Yes)

29.0 (29.0)

No support

(Yes)

10

Feature

Android

Android Webview

Firefox Mobile (Gecko)

IE Mobile

Opera Mobile

Safari Mobile

Chrome for Android

Basic support

No support

(Yes)

27.0 (27.0)

No support

No support

10

39.0

IteratorResult object instead of throwing

No support

?

29.0 (29.0)

No support

No 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)。

规范

Specification

Status

Comment

ECMAScript 2015 (6th Edition, ECMA-262)The definition of 'Iteration' in that specification.

Standard

Initial definition.

ECMAScript Latest Draft (ECMA-262)The definition of 'Iteration' in that specification.

Living Standard

JavaScript

JavaScript 是一种高级编程语言,通过解释执行,是一门动态类型,面向对象(基于原型)的解释型语言。它已经由ECMA(欧洲电脑制造商协会)通过 ECMAScript 实现语言的标准化。它被世界上的绝大多数网站所使用,也被世界主流浏览器( Chrome、IE、FireFox、Safari、Opera )支持。JavaScript 是一门基于原型、函数先行的语言,是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。它提供语法来操控文本、数组、日期以及正则表达式等,不支持 I/O,比如网络