深入理解ES6 ---- 迭代器

955 阅读5分钟

迭代器

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

(1) Iterator 的作用

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

(2) Iterator的迭代过程

所有的迭代器对象都会有一个next方法,每次调用都会返回一个对象:{done: boolean, value: any}value表示当前成员的值,done表示是否还有更多的数据。迭代器内部会维护一个指针,指向当前成员的位置,每次调用next都会指向下一个成员。

// es5实现迭代器
function creatIterator(arr){
    let index = 0;
    return {
      next: () =>{
        return {
          done: index > arr.length - 1,
          value: this.done ? undefined : arr[index++]
        }
      }
    }
}
const iterator = creatIterator([1, 2, 3])
console.log(a.next())  // { done: false, value: 1 }
console.log(a.next())  // { done: false, value: 2 }
console.log(a.next())  // { done: false, value: 3 }
console.log(a.next())  // { done: true, value: undefined }
// 之后的调用都会返回相同的内容
console.log(a.next())  // { done: true, value: undefined }

生成器

生成器是一种返回迭代器的函数。通过function后面的*来表明它是一个生成器。

  • yield关键字是es6的新特性,它可以指定调用next方法时的返回值以及调用顺序
  • 每当执行完yield语句,函数就会停止执行,直到再次调用next方法才会继续执行
  • yield关键字只能在生成器内部使用,其他地方会导致语法错误
function * createIterator(){
   yield 1
   yield 2
}
// 生成器返回的是一个迭代器
const iterator = createIterator()
console.log(iterator.next())  // { done: false, value: 1 }
console.log(iterator.next())  // { done: false, value: 2 }
console.log(iterator.next())  // { done: true, value: undefined }

使用函数表达式:const createIterator = function *(){ yield 1 }但不能使用箭头函数来创建生成器。

这里仅仅介绍了生成器可以返回迭代器,但它主要的作用是流程管理,因此可以使用同步的方式书写异步代码generator详情

可迭代对象

(1) Symbol.iterator属性

一个数据结构只要具有Symbol.iterator属性,就可以认为是可迭代的

  • 所有的集合对象array、set、map以及string都是可迭代对象,都有默认的迭代器,即Symbol.iterator属性
  • 生成器生成的对象就是可迭代的,生成器会默认为他们的Symbol.iterator属性赋值
  • 通过执行Symbol.iterator方法 来获取对象的迭代器

访问数组的默认迭代器:

const arr = [1, 2, 3]
// 通过执行数组的 `Symbol.iterator`方法来获取 `arr` 的迭代器
const arrIterator = arr[Symbol.iterator]()

console.log(arrIterator.next())  // { value: 1, done: false }
console.log(arrIterator.next())  // { value: 2, done: false }
console.log(arrIterator.next())  // { value: 3, done: false }
console.log(arrIterator.next())  // { value: undefined, done: true }

(2) for...of

for...of封装了next的重复调用过程,来自动执行迭代器,遍历访问可迭代对象的成员。它通过对象的Symbol.iterator方法来获取迭代器,并在每次循环中调用可迭代对象的next方法,将返回值根据迭代器的规则赋给中间变量,一直循环到done属性为true。即

const arr = [1, 2, 3]
for(let item of arr){
    console.log(item)
}
// 1 2 3

(3) 创建可迭代对象

可以通过给一个不可迭代对象设置Symbol.iterator属性,使它变成可迭代的。Symbol.iterator必须是迭代器生成函数,否则使用for...of会报错。

// es5
const list = {
  items: [1,2,3],
  [Symbol.iterator](){
    let index = 0;
    return {
      next: () =>{
        return {
          done: index > this.items.length - 1,
          value: this.done ? undefined : this.items[index++]
        }
      }
    }
  }
}
for (const item of list) {
  console.log(item)
}
// 1 2 3

// es6
const list = {
  items: [1,2,3],
  *[Symbol.iterator](){
    for(item of this.items){
      yield item
    }
  }
}
for (const item of list) {
  console.log(item)
}
// 1 2 3

(3) 内置迭代器

entries、values、keys都是es6新增的迭代器。下表为各种集合类型的各种迭代器在for...of循环中的中间变量:

集合类型/迭代器 entries values keys
array [[index, value]] [value] [index]
map [[key, value]] [value] [key]
set [[value, value]] [value] [value]

每个集合类型都有默认的迭代器,在for... of循环中,如果没有显式的指定,则使用默认的迭代器。上表中的✔表示各集合类型的for... of默认迭代器

const arr = [1,2,3]
// 没有显示指定迭代器,则使用array默认的迭代器,即values迭代器
for (const item of arr) {
  console.log(item)
}
// 1 2 3

// 使用显示指定的entries迭代器
for (const item of arr.entries()) {
  console.log(item)
}
// [0,1]  [1,2]  [2,3]

需要注意的是:entries、values、keys请勿与Object.entries、Object.values、Obejct.keys混淆。前者返回的是迭代器,用于for...of循环;后者返回的是根据原对象格式化后的数组

(4) 字符串迭代器

es5中也可以使用for循环遍历字符串,但却以编码为单元而非字符,因此无法正确访问双字节字符es6中全面支持unicode,所以**for...of循环就可以正确遍历字符串中的双字节字符**

const str = '𠮷1'
for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// " "
// " "
// "1"

// 可以正确识别双字节字符
for (let i of text) {
  console.log(i);
}
// "𠮷"
// "1"

(5) NodeList迭代器

DOM标准中有一个NodeList类型,用于表示页面文档元素的集合。它含有length属性;可以通过[number]访问元素,跟数组的格式和操作方法很类似,但其实它是对象,也就是我们俗称的伪数组,比如:{0: domObject, 1: domObject, 2: domObject, length:3}es6NodeList类型也拥有了默认迭代器,可以使用for...of迭代

const nodeList = document.getElementsByClassName('abc')
for (const node of nodeList) {
    console.log(node)
}

(6) 展开运算符

展开运算符可以操作所有可迭代对象,并根据默认迭代器来选取要引用的值,然后读取所有值

const nodeList = document.getElementsByClassName('abc')
console.log([...nodeList])   
// [node, node, node]

const map = new Map()
map.set('name','li yang')
map.set('age', 18)
console.log([...map])  // 调用了map的默认迭代器 entries
// [["name", "li yang"], ["age", 18]]

由于展开运算符可以作用于任意可迭代对象,因此要将可迭代对象装换成数组,这是最简单的方法

系列文章