ES6:Iterator 和 for...of 循环

本小节包括:

  • Iterator(遍历器)的概念
  • 默认 Iterator 接口
  • 调用 Iterator 接口的场合
  • 字符串的 Iterator 接口
  • Iterator 接口与 Generator 函数
  • for...of 循环

一.Iterator(遍历器)的概念

JavaScript 有 4 种数据集合,分别是数组(Array)、对象(Object)、Map 和 Set。而遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。

Iterator 的作用

  • 为各种数据结构提供一个统一的、简便的访问接口;
  • 使得数据结构的成员能够按照某种次序排列;
  • ES6 创造了一种新的遍历命令——for...of 循环,Iterator 接口主要提供 for...of 消费。

二.默认 Iterator 接口

数据结构只要部署了 Iterator 接口,我们就称这种数据结构为“可遍历的”。ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator (内置的 Symbol 值之一)属性上,或者说,一个数据接口只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”。

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

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

例如,数组的 Symbol.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}

对于原生部署 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 }
        }
      }
    };
  }
}

对于类似数组的对象(存在数值键名和 length 属性),部署 Iterator 接口可以直接引用数组的 Iterator 接口。

let iterable = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
}

for (const item of iterable) {
  console.log(item) // 'a', 'b', 'c'
}

三.调用 Iterator 接口的场合

有一些场合会默认调用 Iterator 接口(即 Symbol.iterator 方法)

  • 解构赋值
  • 扩展运算符
  • yield*
  • for...of
  • Array.from()
  • Map()、Set()、WeakMap() 和 WeakSet() (比如 new Map(['a', 1], ['b', 2]))
  • Promise.all()
  • Promise.race()

四.字符串的 Iterator 接口

字符串是一个类似数组的对象,也具有原生 Iterator 接口。

const str = 'hi'
typeof str[Symbol.iterator] // "function"

const iterator = str[Symbol.iterator]()

iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }

五.Iterator 接口与 Generator 函数

Symbol.iterator 方法的最简单实现是使用下一章的 Generator 函数.

const myIterable = {}

myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
}

[...myIterable] // [1, 2, 3]

六.for...of 循环

for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、后面章节的 Generator 对象,以及字符串。

1.数组

for...of 循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟 for..in 循环不同。

let arr = [3, 4, 5]
arr.foo = 'hi'

for (const i in arr) {
  console.log(i) // "0", "1", "2", "foo"
}

for (const j of arr) {
  console.log(j) // 3, 4, 5
}

2.Set 和 Map 结构

需要注意两点:首先,遍历的顺序是按照各个成员被添加进数据结构的顺序;其次,Set 结构遍历时返回的是一个值,而 Map 结构遍历时返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。

let mySet = new Set(['hi', 'hello'])
for (const item of mySet) {
 console.log(item)
}
// "hi"
// "hello"

let myMap = new Map().set('a', 1).set('b', 2)
for (const item of myMap) {
  console.log(item)
}
// ['a', 1]
// ['b', 2]

3.类似数组的对象

字符串、DOM NodeList 对象、arguments 对象的例子

// 字符串
let str = 'hi'

for (const s of str) {
  console.log(s) // h i
}

// DOM NodeList 对象
let paras = document.querySelectorAll('p')

for (const p of paras) {
  p.classList.add('test')
}

// arguments 对象
function printArgs() {
  for (const x of arguments) {
    console.log(x)
  }
}
printArgs('a', 'b') // "a" "b"

4.与其他遍历语法的比较

JavaScript 中最原始的遍历方法就是 for 循环,但这种写法比较麻烦,后来有了 forEach 方法。但 forEach 方法又有缺陷,无法 break  或 return。for...in 循环就不说了,这个适合用来遍历对象。

 

 

你可能感兴趣的:(《ES6标准入门》笔记)