ES6+ 迭代协议

1. 前言

上一节我们对 ES6 新增的 for...of 做了深入的讲解,它可以用于字符串、数组、类数组、以及新增的数据结构 Map/Set 等进行遍历。但是这些能够使用 for...of 进行遍历的都有一个共同的特性 —— 可迭代。那什么是可迭代呢?

ECMAScript 2015 在一组补充规范中规定了两个协议:可迭代协议和迭代器协议。这两个规定不是新的内置实现或语法,而是协议。这些协议可以被任何数据结构遵循,从而可以实现自定义的遍历,而这些遵守迭代协议的数据结构是可以被 for...of 遍历的。有了这样的一个规范,我们所定义的数据结构就会更加丰富了,本节我们将深入 ES6 中的迭代。

2. 可迭代协议和迭代器协议

迭代协议包括两方面的内容 —— 可迭代协议和迭代器协议,下面我们就来看看这两个协议都是什么。

2.1 可迭代协议

什么是可迭代协议?可以通过 JavaScript 对象定义或定制迭代行为,在 JavaScript 中有一些内置类型并且满足内置的可迭代对象,具有迭代的行为。如 Array、Map、Set 等。还有一个比如字面量对象(Object)则没有,如果要对 Object 进行迭代的话需要使用 for...in 循环,但是 for...in 在循环时需要判断是否是自身属性。所以很多时候如果想用 for...of 进行迭代时就需要使用对象上的 Object.keys() 等方法提取对象中的 keys 后再去进行遍历操作。

在 ES6 中可迭代协议规定想要成为可迭代对象,这个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性。所以,一个对象满足可迭代协议的关键在于实现 Symbol.iterator 方法,这个方法的返回值是一个符合迭代器协议的对象,并且是一个无参数的函数。

2.2 迭代器协议

上面说到了在实现 Symbol.iterator 方法时需要返回一个满足迭代器协议的方法。那么迭代器协议又是什么呢?

迭代器协议定义了产生一系列值的一个标准方式,迭起协议规定需要返回一个带 next() 方法的对象。 next() 可以被多次执行,每次执行都会返回一个对象,该对象包含两个属性,donevalue:

  • done 是一个 boolean,在没有迭代完时返回 false,迭代完成后返回 true;
  • value 就是被迭代的返回值,当 done 为 true 时可以省略。

实现了以上两点才会满足一个迭代器协议。一般来说可迭代协议和迭代器协议在实际的场景中是同时存在的。下面我来看看什么是迭代器?并且怎么使用可迭代协议和迭代协议去实现一个迭代器。

3. 迭代器

这里说的迭代器是遵循上面两个协议来实现的,在满足两个协议时,我们可以显式地通过不断调用 next () 方法去进行迭代。在迭代一个迭代器后,我们称之为消耗了这个迭代器而,且每个迭代器只能执行一次。下面我们来看看怎么实现一个迭代器:

var obj = {}
obj[Symbol.iterator] = function() {
  let index = 1;
  return {
    next() {
      if (index <= 10) {
        return {value: index++, done: false}
      } else {
        return {done: true}
      }
    }
  }
}

上面的代码中根据可迭代协议给 obj 对象添加一个 Symbol.iterator 方法,再根据迭代器协议返回一个 next() 方法,在每次消耗 next() 时对 index 进行加 1 操作。当 index 大于 10 的时候结束迭代行为,之后再消耗 next() 返回值不变。

根据上面的代码,我们可以显式的手动调用 next()

var iterator = obj[Symbol.iterator]();
var s = iterator.next();

while(!s.done) {
  console.log(s.value);
  s = iterator.next();
}
// 1
// 2
// ...

执行上面的代码,在浏览器的控制台中,可以看到大于的结果是 1 到 10。上面是我们手动执行消耗 next() 的方式,上面我们也说了,只要满足迭代协议就可以被 for...of 循环,那是不是真的是这样的呢?下面我们就使用 for...of 对 obj 进行循环。

for (let i of obj) {
  console.log(i)
}
// 1
// 2
// ...

在控制台中执行上面的代码,可以看到和我们使用手动调用 next() 方式返回打印的结果是一样的。

4. 小结

本节我们主要学习了两个协议 —— 可迭代协议和迭代器协议,并且通过这两个协议实现了一个迭代器。通过这个迭代器我们知道,在满足这两个协议后就可以使用 for...of 进行循环,并且我们进行显示调用进行了验证。

插入案例