## Iterator和for...of
### Iterator(遍历器)的概念
JavaScript表示集合的数据结构有数组和对象,ES6又增加了 `Set` 和 `Map` ,4种数据结构,并且它们之间还可以相互嵌套。
`Iterator` 就是一种统一的机制,用来访问这些不同的数据结构。
`Iterator` 的遍历过程如下:
- 创建一个对象指针,指向当前数据结构的起始位置,其实它就是一个指针对象
- 第一次调用指针对象的 `next` 方法,指针指向数据结构的第一个成员
- 第二次调用时,指针指向第二个成员
- 以此类推,直到指向最后一个成员
每次调用 `next` 方法都会返回数据结构当前成员的信息,这个信息是一个对象,包含了 `value` 和 `done` 两个属性, `value` 表示当前成员的值, `done` 表示遍历是否结束,是一个布尔值。
下面来模拟一下这个过程:
```js
let it = makeIterator(['a', 'b'])
it.next() // { value: 'a', done: false }
it.next() // { value: 'b', done: false }
it.next() // { value: undefined, done: true }
function makeIterator (array) {
var nextIndex = 0
return {
next: function () {
return nextIndex < array.length ?
{ value: array[nextIndex++], done: false } :
{ value: undefined, done: true }
}
}
}
```
指针对象的 `next` 方法用于移动指针,开始时,指针指向数组的开始位置,然后,每次调用 `next` 方法,指针都会指向数组的下一个成员。
### 默认Iterator接口
`Iterator` 接口的目的是为所有的数据结构提供一种统一的访问机制,即 `for...of`循环。
ES6规定,默认的 `Iterator` 接口部署在数据结构的 `Symbol.iterator` 属性上,一个数据结构只要具有 `Symbol.Iterator` 属性,就可以认为是可遍历的。
调用 `Symbol.iterator` 方法,我们就可以得到当前数据结构默认的遍历器生成函数。它本身是一个表达式,返回 `Symbol` 对象的 `iterator` 属性。
```js
const obj = {
[Symbol.iterator]: function () {
return {
return {
value: 1,
done: true
}
}
}
}
```
ES6的有些数据结构原生具有 `Iterator` 结构,比如数组,即数据可以不做任何处理就可以被 `for...of` 循环遍历。但对象没有。原生具有遍历器属性的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- arguments
- NodeList
```js
let arr = ['a', 'b', 'c']
let iter = arr[Symbol.iterator]()
iter.next() // [value: 'a', done: false]
iter.next() // [value: 'b', done: false]
iter.next() // [value: 'c', done: false]
iter.next() // [value: undefined, done: true]
```
对于原生部署 `Iterator` 接口的数据结构,我们不用自己编写遍历器生成函数, `for...of`循环会自动遍历它们。对象(Object)之所有没有默认部署 `Iterator` 接口,是因为对象属性的遍历先后顺序是不确定的,需要开发者手动指定。
一个对象如果要具备可被 `for...of` 循环调用的 `Iterator` 接口,就必须在 `Symbol.iterator` 属性上部署遍历器生成方法。
```js
class RangeIterator {
constructor (start, stop) {
this.value = start
this.stop = stop
}
[Symbol.iterator]() { return this }
next () {
let value = this.value
if (value < this.stop) {
this.value++
return { done: false, value: value }
}
return { done: true, value: undefined }
}
}
function range (start, stop) {
return new RangeIterator(start, stop)
}
for (let value of range(0, 3)) {
console.log(value) // 0, 1, 2
}
```
### 调用Iterator接口的场合
- 解构赋值:对数组和 `Set` 结构进行解构赋值时,会默认调用 `Symbol.iterator` 方法
- 扩展运算符:扩展运算符(...)也会调用默认的 `Iterator` 接口
- `yield*` :`yield*` 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口
### 字符串的 Iterator 接口
字符串是一个类似数组的对象,也具有原生的 `Iterator` 接口
```js
let someString = 'hi'
typeof someString[Symbol.iterator] // "function"
```
### 遍历器对象的 return()、throw()
遍历器对象除了具有 `next` 方法,还可以具有 `return` 方法和 `throw` 方法。
### for...of 循环
ES6借鉴了其他语言的特性,引入了 `for...of` 循环作为遍历所有数据结构的统一的方法。
一个数据结构只要部署了 `Symbol.iterator` 属性,那么它就会被视为具有 `iterator` 接口,就可以使用 `for...of` 循环遍历它的成员。
`for...of` 循环可以使用的范围包括数组、`Set` 和 `Map` 结构、某些类似数组的对象,比如 `arguments` 对象、`DOM NodeList` 对象、`Generator` 对象,以及字符串。
#### 数组
数组原生具备 `iterator` 接口。
```js
const arr = ['red', 'green', 'blue']
for (let v of arr) {
console.log(v) // red green blue
}
```
#### Set和Map
`Set` 和 `Map` 结构原生具有 `Iterator` 接口,可以直接使用 `for...of` 循环。
```js
let engines = new Set(['Gecko', 'Trident', 'Webkit', 'Webkit'])
for (let e of engines) {
console.log(e)
}
// Gecko
// Trident
// Webkit
let es6 = new Map()
es6.set('edition', 6)
es6.set('committee', 'TC39')
es6.set('standard', 'ECMA-262')
for (let [name, value] of es6) {
console.log(name + ":" + value)
}
```
#### 类似数组的对象
```js
// 字符串
let str = 'hello'
for (let s of str) {
console.log(s) // h e l l o
}
// DOM NodeList对象
let paras = document.querySelectorAll('p')
for (let p of paras) {
p.classList.add('test')
}
// arguments对象
function printArgs () {
for (let x of arguments) {
console.log(x)
}
}
printArgs('a', 'b') // 'a', 'b'
```
#### 对象
对于普通的对象,`for...of` 结构不能直接使用,否则会报错,必须部署了 `Iterator` 接口才能使用,在这种情况下,`for...in`仍然是可以使用的。
```js
let es6 = {
edition: 6,
committee: 'TC39',
standard: 'ECMA-262'
}
for (let e in es6) {
console.log(e)
}
// edition
// committee
// standard
for (let e of es6) {
console.log(e)
}
// TypeError: es6[Symbol.iterator] is not a function
```
对于这种情况一般的解决办法是,使用 `Object.keys` 方法将对象的键名生成一个数组,然后遍历这个数组
```js
for (let key of Object.keys(someObject)) {
console.log(key + ': ' + someObject[key])
}
```
另外一个方法是使用 `Generator` 函数将对象重新包装一下
```js
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]]
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value)
}
// a -> 1
// b -> 2
// c -> 3
```