如何使用 for...of 循环遍历对象

135 阅读2分钟

如何使用 for...of 循环遍历对象

引题

今天在群里碰到一个题目,正好最近在看阮一峰的 ES6,所以记录一下:

定义一个 class 类 Book,传入一个数组后可以用 for...of...遍历,即

class Book {
  // TODO
}

const book = new Book(['lonely', 'happy', 'rich'])
for (let item of book) {
  console.log(item) // 依次输出'lonely', 'happy', 'rich'
}

先说一下最终答案:

class Book {
  constructor(book = []) {
    this.data = book
  }
  [Symbol.iterator]() {
    let index = 0
    return {
      next: () => {
        return {
          value: this.data[index++],
          done: index > this.data.length
        }
      }
    }
  }
}

提到for...of循环,就得提到遍历器-Iterator

Iterator

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

它的作用主要由三个:

  1. 提供统一的访问接口

  2. 使数据结构的成员能够按照次序排列

  3. ES6 新增了新的遍历命令for...of循环,Iterator 接口主要供for...of消费

它的遍历过程是这样的:

  1. 创建一个指针对象,指向当前数据结构的起始位置,也就是说,遍历器对象本质就是一个指针对象

  2. 第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员

  3. 第二次调用指针对象的 next 方法,就指向第二个成员

  4. 不断调用 next 方法,直到指向数据结束为止

每次调用 next 方法都会返回一个包含valuedone两个属性的对象,其中value属性是当前成员的值,done是一个布尔值,代表遍历是否结束。

默认 Iterator 接口

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,即一个数据结构只要有 Symbol.iterator 属性就代表是可遍历的。Symbol.iterator 属性本身是一个函数,执行这个函数会返回一个遍历器。

一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是可遍历的[Iterable]

ES6 中有些数据结构原生具备 Iterator 接口

  • Array

  • Map

  • Set

  • String

  • TypedArray

  • 函数的 arguments 对象

  • NodeList 对象

对象 Object 则没有部署,因为对象的属性遍历顺序是不确定的,想要实现 Object 被for...of循环调用,则可以在原型上添加遍历器。

Object.prototype[Symbol.iterator] = function () {
  let index = 0
  const keys = Object.keys(this)
  return {
    next: () => {
      return {
        value: this[keys[index++]],
        done: index > keys.length
      }
    }
  }
}