你真的了解 for of 嘛?

在ES6中借鉴了 c++Javac#python 等语言,提供了一种简单统一的接口,for of 循环。for of 循环实际调用了 Symbol.iterator 接口。

1. 简单使用

先看一下 for of 的简单用法。

const arr = [1, 2, 3, 4, 5];

for (const i of arr) {
  console.log(i);
}

从上面可以看到,for of 使用非常方便,也很容易理解。
部署过 Symbol.iterator 接口的数据结构都可以使用 for of 进行迭代。

部署过 iterator 接口的数据结构:

array、map、set、string、TypeArray、nodeList、arguments

可以看到,对象是不包括在内的。对象没有部署 iterator 接口,无法被迭代。
不过,我们可以为对象手动部署 iterator 接口,用于实现对象的迭代。

2. 关于 Symbol.iterator

上面一直说 Symbol.iterator 接口,那这个接口到底是什么?

以Map为例,我们来看一下它的原型(为啥要用Map举例,自己想)。

of1.png

可以看到图片中标红的部分,这是内置的 iterator 接口,正因为部署过这个接口,我们才能肆无忌惮的使用 for of 进行迭代。
还可以看到这个属性的值指向的是 entries 方法,所以当我们使用 for of 迭代 Map 时,迭代的数据实际上调用 entries 方法得到的数据。

嗯,应该不会有人问 Symbol 是什么东西吧。思考中。

3. 对象部署迭代器

a. 迭代指定数据

先看一个简单实例,后面再对其继续优化。

const obj = {
  start: [1, 3, 2, 4],
  end: [5, 7, 6],
  [Symbol.iterator] () {
    let index = 0,
        arr = [...this.start, ...this.end],
        len = arr.length;

    return {
      next () {
        if (index < len) {
          return {
            value: arr[index++],
            done: false
          }
        } else {
          return {
            value: undefined,
            done: true
          }
        }
      }
    }
  }
}
for (let i of obj) {
  console.log(i);
}

// 1 3 2 4 5 7 6

上面可以看到,我们为对象部署了一个迭代器接口。

案例中合并start和end数组,并提供迭代的方法。返回值是一个对象。提供 valuedone 两个参数。下面对其进行优化,让接口的功能更加强大。

b. 对象部署迭代接口

实现思路:将对象转换为Map,然后再进行迭代处理。

const obj = {
  a: 1,
  b: 2,
  c: 3,
  [Symbol.iterator] () {
    const map = new Map();

    for (const [key, value] of Object.entries(this)) {
      map.set(key, value);
    }

    let mapEntries = [...map.entries()],
        nextIdx = 0,
        length = mapEntries.length;

    return {
      next () {
        return nextIdx < length ? { value: mapEntries[nextIdx++], done: false }
                                : { value: undefined, done: true }
      }
    }
  }
}
for (const [key, value]  of obj) {
  console.log(key, value);
}

// a 1
// b 2
// c 3

现在已经可以肆意的迭代对象啦。多提一嘴,迭代器除了可以部署 next 方法,还可以部署 returnthrow 方法。

c. 对象部署迭代接口优化

上面的方法还可以使用 generator 优化一下。

const obj = {
  a: 1,
  b: 2,
  c: 3,
  [Symbol.iterator]: function * () {
    const map = new Map();

    for (const [key, value] of Object.entries(this)) {
      map.set(key, value);
    }

    let mapEntries = [...map.entries()],
        nextIdx = 0,
        length = mapEntries.length;

    while (nextIdx < length) {
      yield mapEntries[nextIdx++];
    }
  }
}
for (const [key, value]  of obj) {
  console.log(key, value);
}

// a 1
// b 2
// c 3

ok,现在为对象部署的迭代接口就比较完美啦。

4. 迭代器相关

简单说明一下迭代器在前端中是不区分内部迭代器和外部迭代器的。

内部迭代器:

系统内部定义好的迭代规则,调用时能够一次性拿到所有的元素,这种就做外部迭代器。

外部迭代器:

自定义部署的迭代器接口,就是外部迭代器。

默认使用 iterator 接口的场景

1. 拓展运算符
2. for of 
3. Array.from
4. map、set
5. Promise.all、Promise.race
6. generator yield
...

总结

本文粗略介绍了一下 for of,希望对大家有所帮助。