/ ECMAScript6

ECMAScript6 之 Iterator & for…of

标签: ECMAScript 6 javaScript

Iterator 概念

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

具体到语言层面来说,实现了以下几个东西

  1. 规定对象下部署一个统一的方法
  2. 统一的方法返回了统一的结构
  • 上面两点中的统一方法的方法名就叫: Symbol.iterator
  • 返回的结构统一为:{ next: function }
  • next 方法有统一的返回值结构:{ value: any, done: boolean }
  • ES6 中,Array/Map/Set/String 等对象都部署了以上内容

对于部署了上述特性的对象,有一个统一的名字:可遍历对象。当然我们也可以自己在一个对象下部署以上内容,也就是说:可遍历对象是可以由我们自己创建的。

在深入的想一想:由我们自己实现的 next 那么 next 的返回值也是由我们自己来控制,那么这个遍历的过程,遍历的结果也都是可由我们自己来定义的。

Iterator 接口

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即 for...of 循环(详见下文)。当使用 for...of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。

ES6 规定,默认的 Iterator 接口部署在对象的 Symbol.iterator 属性上,当该属性有值时,该对象即是可遍历对象。

一个简单的可遍历对象:

const obj = {
    [Symbol.iterator]: function () {
        return {
          next: function () {
            return {
              value: 1,
              done: true
            }
          }
        }
    }
}

由于 obj 对象下部署了 Symbol.iterator 方法,那么该对象即是可遍历对象,如上述所说,执行该属性会获得一个具有 next 属性的对象,每次调用返回对象的 next 方法,即可获得 {value: any, done: boolean} 结构的数据。

常见原生就具备 Iterator 接口的对象:

  • Array
  • Map
  • Set
  • String
  • 函数的 arguments 对象
  • NodeList 对象

以下说明数组原生具备 Iterator 接口

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 }

for...of

一个对象如果要具备可被 for...of 循环调用的 Iterator 接口,就必须在 Symbol.iterator 的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

使用默认 Iterator 接口

let arr = ['a', 'b', 'c']

for(let value of arr){
    console.log(value) // a b c
}

使用原型链上的 Iterator 接口

class RangeIterator {
    constructor(start, stop) {
        this.value = start;
        this.stop = stop;
    }
    
    [Symbol.iterator]() {
        return this
    }
    
    next() {
        var 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
}

遍历过程

当有方法或者动作需要进行遍历对象的时候,会进行以下步骤

  1. 先调用 Symbol.iterator 方法,获得一个遍历函数
  2. 开始遍历,遍历一次调用一次 next 方法
  3. 获得返回值,取到 value 属性并操作
  4. 判断 done 属性,若为 true 则停止循环,反之开始下一次遍历

作用

以下过程都会经历上述的遍历过程

  • 解构赋值
let set = new Set().add('a').add('b').add('c')

let [x,y] = set
// x='a'; y='b'

let [first, ...rest] = set
// first='a'; rest=['b','c']
  • 扩展运算符
// 例一
var str = 'hello'
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c']
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
  • Promise.all()
  • Promise.race()

遍历器的 return()

遍历器除了拥有 next 方法,还可以拥有 return 方法, return 方法主要用于循环中途退出的情况,用于释放内存,以及及时停止遍历器。

function readLinesSync(file) {
    return {
        [Symbol.iterator]() {
            return {
                next() {
                    return { done: false }
                },
                return() {
                    file.close()
                    return { done: true }
                }
            }
        }
    }
}

// 情况一:由 break 触发 return
for (let line of readLinesSync(fileName)) {
    console.log(line)
    break
}

// 情况二:由 error触发 return
for (let line of readLinesSync(fileName)) {
    console.log(line)
    throw new Error()
}

注: 上面的例子大多数来自《ECMAScript 6入门》这本书。