iterator 与 generator

288 阅读2分钟

1. 什么是 iterator

iterator 是一个定义在对象的 Symbol.iterator 属性上的方法执行时返回的对象, 常见在 Array, String , Map 等上

const arr = [1, 2, 3, 4]
console.log(arr[Symbol.iterator]()) //=> Array Iterator {}

当我们使用 ... 展开运算符去展开一个数组时, 其内部会使用到 iterator
当我们使用 for of 循环时, 其内部也会使用到 iterator ...

console.log(...arr) //=> 1 2 3 4

for (const item of arr) {
  console.log(item)
  //=> 1
  //=> 2
  //=> 3
  //=> 4
}

然而, 没有 Symbol.iterator 方法的对象是无法使用这些功能的

const obj = {0: 1, 1: 2, 2: 3, 3: 4, length: 4}
console.log(obj[Symbol.iterator]) //=> undefined
console.log(...obj) // Uncaught TypeError: Found non-callable @@iterator

2. 如何使用 iterator

根据迭代器协议: 一个迭代器对象拥有 next() 方法, 且遵循如下语义

next 的返回对象中的值意义
value迭代器所获取的序列的某一个值
done如果通过迭代器能获取序列的下一个值, 则 done 为 true, 否则为 false

既然是方法, 那么就应该可以执行

const it = arr[Symbol.iterator]()
console.log(it.next()) //=> {value: 1, done: false}
console.log(it.next()) //=> {value: 2, done: false}
console.log(it.next()) //=> {value: 3, done: false}
console.log(it.next()) //=> {value: 4, done: false}
console.log(it.next()) //=> {value: undefined, done: true}

3. 自己实现一个简单的 iterator 获取方法

const getArrayIterator = (array) => {
  let cur = 0
  let state = false
  const myIterator = {
    next() {
      let done = cur >= array.length
      let value = done ? undefined : array[cur ++]
      return done ? {value: undefined, done: true} : { value, done }
      // 当返回的对象中 done 为 false 时, 即使数组再添加元素
      // 调用 next 方法也不会返回序列中的值了
    },
    // 返回的 iterator 对象也能够返回 iterator 对象, 可以使用 ... 和 for of
    [Symbol.iterator]() {
      return this
    }
  }
  return myIterator
}

const it0 = getArrayIterator([1, 2, 3, 4, 5])
const it1 = getArrayIterator([2, 4, 6, 8, 10])

console.log(...it0) //=> 1 2 3 4 5

for (const item of it1) {
  console.log(item)
  //=> 2
  //=> 4
  //=> 6
  //=> 8
  //=> 10
}

4. generator

generator 是执行 generator function 时的返回值, 其是一个对象, 且遵循迭代器协议

每次对 generator 使用 next 方法

  • done 不为 true 时, 其返回的对象中的 value 都对应一个 yield 的值
  • donetrue 时, value 对应的是 generator functionreturn 所对应的值
// generator function
function * gen() {
  yield 1
  yield 2
  return 4
}
console.log(...gen()) //=> 1 2

// generator object
const it = gen()
console.log(it.next()) //=> {value: 1, done: false}
console.log(it.next()) //=> {value: 2, done: false}
console.log(it.next()) //=> {value: 4, done: true}

5. 由 generator 引出的 next 方法的传参使用

n 次使用 next 时, 所传的参数会被当做第 n - 1yield 的返回值(第一次调用 next 方法时传参并没有用)

function * gen() {
  let a = yield 1
  let b = yield 2
  return a + b
}

let it = gen()
console.log(it.next())   //=> {value: 1, done: false}
console.log(it.next(10)) //=> {value: 2, done: false}
console.log(it.next(20)) //=> {value: 30, done: true}