JS 中表示“集合”的数据结构有:Array、Object、Map、Set等。

需求:需要统一的接口机制,遍历不同表示“集合”的数据结构。

解决方案:遍历器(Iterator)就是这个接口,针对不同的数据结构完成都可遍历。

一般项目里其实不太用使用Iterator,但是理解这个,可能是理解其他的基础,比如生成器。

Iterator 的遍历过程

遍历器对象本质上,就是一个指针对象(联想遍历各种结构的指针)。

(1)创建一个指针对象,指向当前数据结构的起始位置。,

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

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

怎么添加 Iterator接口

其实就是在数据结构中增加一个Symbol.iterator属性,其是一个返回**Iterator(遍历器)**的函数。

遍历器的本质是个对象,有个next方法(next的本质是函数,所以能调用),其是一个返回{value:xx,done:true/false}的函数。

value属性返回当前位置的成员,done属性是一个布尔值,表示是否遍历结束,即是否还有必要再一次调用next方法。

for of遍历数据结构,就可以得到每次的value

var obj = {
  [Symbol.iterator]: function () {
    const keys = Object.keys(this)
    let p = 0
    return { next: () => {
      const res = { value: this[keys[p]], done: keys.length < p + 1 }
      p++
      return res
    } }
  },
  a: 1,
  b: 2
}
let it = obj[Symbol.iterator]()
// 所谓的迭代器就是一个对象
console.log(it) // { next: [Function: next] }
console.log(it.next()) // { value: 1, done: false }
console.log(it.next()) // { value: 2, done: false }
console.log(it.next()) // { value: undefined, done: true }

for (let v of obj) {
  // 和上面一样,输出两次,1、2
  console.log(v)
}

复制代码

也可使用生成器添加 Iterator接口

生成器执行的时候,直接返回遍历器实例。

var obj = {
  [Symbol.iterator]: function *  () {
    yield 1
    yield 2
  },
  a: 1,
  b: 2
}
let it = obj[Symbol.iterator]()
console.log(it) // Object [Generator] {}
console.log(it.next()) // { value: 1, done: false }
console.log(it.next()) // { value: 2, done: false }
console.log(it.next()) // { value: undefined, done: true }

for (let v of obj) {
  // 和上面一样,输出两次,1、2
  console.log(v)
}
复制代码

天生有Iterator接口的数据结构

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

注意!!!对象没有内置Iterator接口,所以如果不是手动添加,不能使用for of遍历

用数组举例看下:

var arr = [1, 2]
let it = arr[Symbol.iterator]()
console.log(it) // { next: [Function: next] }
console.log(it.next()) // { value: 1, done: false }
console.log(it.next()) // { value: 2, done: false }
console.log(it.next()) // { value: undefined, done: true }

for (let v of arr) {
  // 和上面一样,输出两次,1、2
  console.log(v)
}

复制代码

遍历器的使用场景

  • 解构
  • 扩展运算符
  • 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口
    • for...of
    • Array.from()
    • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
    • Promise.all()
    • Promise.race()

和其他遍历语法的比较

主要以遍历数组为例:

  • for:比较繁琐
  • forEach:无法中途跳出forEach循环
  • for in:遍历数组的键名(索引),还有原型链上的键。当然一般遍历对象用的

for of的优点:

  • 有着同for...in一样的简洁语法,但输出的是是当前键对应的值。
  • 可以break、continue和return配合使用。
  • 提供了遍历所有数据结构的统一操作接口。

注意对象不能直接使用for of,因为内置没有Iterator