> For the complete documentation index, see [llms.txt](https://1425816423.gitbook.io/my-knowledge-base/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://1425816423.gitbook.io/my-knowledge-base/qian-duan-ji-shu/javascript/es6/ni-bu-zhi-dao-de-for...of.md).

# 你不知道的for...of

ES6提供了一种新的遍历方法`for...of`，对于遍历数组，`for...in`遍历的是数组的索引，而`for...of`则是遍历数组的元素。事实上，当使用`for...of`循环遍历某种数据结构时，该循环会自动去寻找 Iterator （遍历器）接口。默认的Iterator 接口部署在数据结构的`Symbol.iterator`属性，而没有默认Iterator接口的数据结构则需要开发者手动指定。

## 什么是Iterator

在js中，表示集合概念的有四种数据结构： 对象（Object）、数组（Array）、Set和Map，用户可以单独使用其中一种数据集合，或者是组合使用，ES6为此提供一种统一的接口机制，用来处理所有不同的数据结构，这种机制就是`遍历器（Iterator）`。

Iterator 的作用有三个：一是为各种数据结构，提供一个统一的、简便的访问接口；二是使得数据结构的成员能够按某种次序排列；三是 ES6 创造了一种新的遍历命令`for...of`循环，Iterator 接口主要供`for...of`消费。

Iterator 的遍历过程是这样的。

（1）创建一个指针对象，指向当前数据结构的起始位置。

（2）第一次调用指针对象的`next`方法，可以将指针指向数据结构的第一个成员。

（3）第二次调用指针对象的`next`方法，指针就指向数据结构的第二个成员。

（4）不断调用指针对象的`next`方法，直到它指向数据结构的结束位置。

从此可以看出：**遍历器对象本质上，就是一个指针对象**

下面我们模拟一个Iterator接口：

```js
function iterator(arr) {
    let nextIndex = 0;
    return {
        next: function () {
            return nextIndex < arr.length ?
                {
                    value: arr[nextIndex++],
                    done: false
                } :
                {
                    value: undefined,
                    done: true
                };
        },
    };
}

const it = iterator([1, 2, 3]);
console.log(it.next()); // {value: 1, done: false}
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: undefined, done: true}
```

上面代码定义了一个`iterator`函数，它是一个遍历器生成函数，作用就是生成一个遍历器对象。对数组`[1, 2, 3]`执行这个函数，就会返回该数组的遍历器对象（即指针对象）`it`。

指针对象`it`包含一个`next()`方法，这个方法没调用一次，都会返回一个包含`value`属性和`done`属性的对象，同时指针移到下一个位置。`it.next()`返回的对象包表示当前数据成员的信息，`value`返回当前数据成员，`done`是一个布尔值，表示遍历是否结束。

## 默认的Iterator接口

ES6 规定，默认的 Iterator 接口部署在数据结构的`Symbol.iterator`属性，或者说，一个数据结构只要具有`Symbol.iterator`属性，就可以认为是“可遍历的”（iterable）。

```js
let arr = [1, 2, 3];
// 注意`Symbol.iterator`是表达式，因此不用加双引号。
console.log(arr[Symbol.iterator]);
//  values() { [native code] }
```

`Symbol.iterator`属性本身是一个函数，就是当前数据结构默认的遍历器生成函数。执行这个函数，就会返回一个遍历器。至于属性名`Symbol.iterator`，它是一个表达式，返回`Symbol`对象的`iterator`属性。

为了验证默认的Iterator接口是否和我们之前模拟的接口一样，我们可以调用数组的Iterator

```js
let arr = [1, 2, 3];
let it = arr[Symbol.iterator]();
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: 3, done: false}
console.log(it.next()); // {value: undefined, done: true}
```

判断数据是否可以用`for...of`遍历，只需要判断是否有`Symbol.iterator`即可

```js
let arr = [1,2,3]
if(arr[Symbol.iterator]){
   for(let item of arr){
       // 
   }
}
```

不过我们平时写代码时很少这样做，因为数组、map、set这些数据结构原生具备 Iterator 接口，即不用任何处理，就可以被`for...of`循环遍历。

原生具备 Iterator 接口的数据结构如下。

* Array
* Map
* Set
* String
* TypedArray
* 函数的 arguments 对象
* NodeList 对象

对象（Object）之所以没有默认部署 Iterator 接口，是因为对象的哪个属性先遍历，哪个属性后遍历是不确定的，需要开发者手动指定。一个对象如果要具备可被`for...of`循环调用的 Iterator 接口，就必须在`Symbol.iterator`的属性上部署遍历器生成方法（原型链上的对象具有该方法也可）。

## 为对象手写一个Iterator接口

原生对象（Object）是没有默认Iterator的，因此如果要用`for...of`方法遍历，需要我们为对象添加一个Iterator接口。

```js
let o = {
    a: "a",
    b: 1,
    c: true,
			[Symbol.iterator]: function () {
        let index = 0;
        let self = this;
        return {
            next: function () {
                let keys = Object.keys(o);
                return index < keys.length ?
                    {
                        value: self[keys[index++]],
                        done: false
                    } :
                    {
                        value: undefined,
                        done: true
                    };
            },
        };
    },
};

for (let item of o) {
    console.log(item);
}
// a
// 1
// true
```

## 哪些场合会用到Iterator

有一些场合会默认调用 Iterator 接口（即`Symbol.iterator`方法）:

* 数组和 Set 结构进行解构赋值时
* 扩展运算符(...)
* yield\*
* for...of
* Array.from()
* Map(), Set(), WeakMap(), WeakSet()（比如`new Map([['a',1],['b',2]])`）
* Promise.all()
* Promise.race()

## 参考

《JavaScript高级程序设计第四版》

《深入理解ES6》

[网道-ES6](https://wangdoc.com/es6/iterator.html)
