JS 迭代协议

🌙
手机阅读
本文目录结构

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 时可省略。

next 方法必须要返回一一个对象,该对象有两个必要的属性: done 和 value,如果返回一个非对象值(比如 false 和 undefined) 会展示一个 TypeError (“iterator.next() returned a non-object value”) 的错误

不可能知道一个特定的对象是否实现了迭代器协议,然而创造一个同时满足迭代器协议和可迭代协议的对象是很 容易的(就像下面的 example 所示)。

这样做允许一个迭代器能被不同希望迭代的语法方式所使用。

因此,很少只实现迭代器协议而不实现可迭代协议。

var myIterator = {
    next: function() {
        // ...
    },
    [Symbol.iterator]: function() { return this }
}

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

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 (展开语法:[…val]),内部也使用了同样的迭代协议:

[...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]

https://a.axihe.com/img/api/jses/Code_TTEVwdEaSg.png

接受可迭代对象的内置 API

许多 API 接受可迭代对象(作为参数,译注), 例如: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]

AXIHE / 精选资源

浏览全部教程

面试题

学习网站

前端培训
自己甄别

前端书籍

关于朱安邦

我叫 朱安邦,阿西河的站长,在杭州。

以前是一名平面设计师,后来开始接接触前端开发,主要研究前端技术中的JS方向。

业余时间我喜欢分享和交流自己的技术,欢迎大家关注我的 Bilibili

关注我: Github / 知乎

于2021年离开前端领域,目前重心放在研究区块链上面了

我叫朱安邦,阿西河的站长

目前在杭州从事区块链周边的开发工作,机械专业,以前从事平面设计工作。

2014年底脱产在老家自学6个月的前端技术,自学期间几乎从未出过家门,最终找到了满意的前端工作。更多>

于2021年离开前端领域,目前从事区块链方面工作了