## 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
```