ES6 新特性之 Iterator 迭代器和 for...of 循环

Iterator 概述

遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署 Iterator 接口,就可以完成遍历操作

作用:

  • 为各种数据结构,提供一个统一的、简便的访问接口
  • 使数据结构的成员能够按某种次序排列
  • 供 for...of 遍历命令消费
let arr = [ 4, 5, 6 ]
for ( let item of arr) {
  console.log(item)
}
// 4
// 5
// 6

原生具备 Iterator 接口的数据结构如下(无需手动部署):

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

Iterator 的遍历过程

(1)创建一个指针对象,指向当前数据结构的起始位置。遍历器对象本质上,就是一个指针对象
(2)第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员
(3)第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员
(4)不断调用指针对象的 next 方法,直到它指向数据结构的结束位置

下面是一个模拟
Iterator 遍历过程的例子

function diyIterator (array) {
  let nextIndex = 0
  return {
    next: function () {
      return nextIndex < array.length ?
        { value: array[nextIndex++], done: false } :
        { value: undefined, done: true }
    }
  }
}

let it = diyIterator(['a', 'b'])
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

默认 Iterator 接口

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即 for...of 循环

当使用 for...of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)

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

// 数组的 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 }
注:Symbol.iterator 是属性名表达式,所以需要 []

一些没有原生部署 Iterator 接口的数据结构(例如对象), 需要自己在 Symbol.iterator 属性上面部署,这样才会被 for...of 循环遍历

// 对象手动部署 Iterator 接口示例
let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator] () {
    const self = this
    let index = 0
    return {
      next () {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          }
        } else {
          return { value: undefined, done: true }
        }
      }
    }
  }
}

下面是通过遍历器实现指针结构的例子:

function Obj (value) {
  this.value = value
  this.next = null
}

Obj.prototype[Symbol.iterator] = function () {
  let current = this
  return {
    next () {
      if (current) {
        let value = current.value
        current = current.next
        return {
          value,
          done: false
        }
      } else {
        return {
          value: null,
          done: true
        }
      }
    }
  }
}

let o1 = new Obj(1)
let o2 = new Obj(2)
let o3 = new Obj(3)

o1.next = o2
o2.next = o3

for (let i of o1){
  console.log(i) // 1, 2, 3
}

遍历器的 return 和 throw

遍历器对象除了具有 next 方法,还可以具有 return 方法和 throw 方法

return 方法的使用场合是,如果 for...of 循环提前退出(通常是因为出错,或者有 break 语句),就会调用 return 方法

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

上面代码中,除了部署 next 方法,还部署了return 方法,下面的两种情况,都会触发执行 return 方法

// 情况一
for (let line of readLinesSync(fileName)) {
  console.log(line)
  break
}

// 情况二
for (let line of readLinesSync(fileName)) {
  console.log(line)
  throw new Error()
}

throw 方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法

本文转载自 http://es6.ruanyifeng.com/#docs/iterator 并进行整理

除特殊说明外本人博客均属原创,转载请注明出处:http://blog.johnhan.cn/blog_1046.html
鄂ICP备17018604号-1  鄂公网安备42060702000030号