迭代器与生成器

2022年7月4日大约 7 分钟

迭代器

迭代器是一个符合迭代器协议的对象。迭代器协议规定了产生一系列值的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。

迭代器协议要求实现 next 方法,next 是一个无参数或接受一个参数的函数,返回一个有 donevalue 属性的对象:

  • done:如果迭代器可以产生序列中的下一个值,则为 false。如果迭代器已将序列迭代完毕,则为 true
  • value:迭代器返回的属性值,donetrue 时可省略。
const array = [1, 2, 3]

// 创建数组迭代器的函数
function createArrayIterator(arr) {
  let index = 0
  return {
    next: function () {
      if (index < arr.length) {
        return { value: arr[index++], done: false }
      } else {
        return { value: undefined, done: true }
      }
    },
  }
}

const arrayIterator = createArrayIterator(array)
console.log(arrayIterator.next()) // { value: 1, done: false }
console.log(arrayIterator.next()) // { value: 2, done: false }
console.log(arrayIterator.next()) // { value: 3, done: false }
console.log(arrayIterator.next()) // { value: undefined, done: true }
console.log(arrayIterator.next()) // { value: undefined, done: true }

可迭代对象

可迭代对象时一个符合可迭代协议的对象,可迭代协议允许对象自定义它们的迭代行为。

要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链open in new window上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iteratoropen in new window 访问该属性。

可迭代协议要求 [Symbol.iterator] 是一个无参数函数,返回一个符合迭代器协议的对象。

// 自定义数组迭代器
const iterableObj = {
  array: [1, 2, 3],
  // 可计算属性
  [Symbol.iterator]: function () {
    let index = 0
    return {
      // 使用箭头函数,this 指向 iterableObj,否则 this 指向 return 的这个对象
      next: () => {
        if (index < this.array.length) {
          return { value: this.array[index++], done: false }
        } else {
          return { value: undefined, done: true }
        }
      },
    }
  },
}

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

// 数组原生迭代器
const array = [1, 2, 3]
const NativeArrayIterator = array[Symbol.iterator]()
console.log(NativeArrayIterator.next()) // { value: 1, done: false }
console.log(NativeArrayIterator.next()) // { value: 2, done: false }
console.log(NativeArrayIterator.next()) // { value: 3, done: false }
console.log(NativeArrayIterator.next()) // { value: undefined, done: true }

for (const item of iterableObj) {
  console.log(item) // 1, 2, 3
}

Array、String、Set、Map、argumetns、NodeList 均是可迭代对象

应用:

  • for ...of、展开运算符、yield*、解构赋值
  • new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable])
  • Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable)

for in 与 for of

for in 遍历的是普通对象的可枚举属性(键名),for of 遍历的是可迭代对象的可迭代元素(值):

Object.prototype.objCustom = function () {}
Array.prototype.arrCustom = function () {}

let arr = ['a', 'b']
arr.foo = 'hello'

// for in 遍历键名
for (let i in arr) {
  console.log(i) // 打印:0 1 foo arrCustom objCustom
}

// for of 遍历键值,不遍历 objCustom、arrCustom 和实例属性 foo
for (let i of arr) {
  console.log(i) // 打印:a b
}

自定义类的迭代

// 定义一个班级类, 创建出来的对象是可迭代对象
class Class {
  constructor(classNum, students) {
    this.classNum = classNum
    this.students = students
  }

  addStudent(newStudent) {
    this.students.push(newStudent)
  }

  [Symbol.iterator]() {
    let index = 0
    return {
      next: () => {
        if (index < this.students.length) {
          return { value: this.students[index++], done: false }
        } else {
          return { value: undefined, done: true }
        }
      },
      // 迭代器的中断
      return: () => {
        console.log('迭代器提前终止')
        return { value: undefined, done: true }
      },
    }
  }
}

const class1 = new Class('1班', ['小明', '小红', '小王'])
class1.addStudent('小刚')

for (const stu of class1) {
  console.log(stu)
  if (stu === '小王') break
}

生成器

生成器是特殊的迭代器,可以控制函数的执行与暂停。

一般地,函数终止条件是返回值或发生异常。使用生成器控制函数的执行与暂停。

通过 function* 定义生成器函数,执行生成器函数会返回一个生成器对象,执行生成器对象的 next 方法,会依次执行生成器函数里被 yield 分割的段代码,并返回 {value: undefined, done: false},结束则 done: true

function* foo() {
  // 第一段代码
  console.log('函数开始执行')
  const value1 = 1
  console.log('第一段代码:', value1)
  yield
  // 第二段代码
  const value2 = 2
  console.log('第二段代码:', value2)
  yield
  // 第三段代码
  console.log('函数执行结束')
}

// 执行生成器函数会返回一个生成器对象
const generator = foo()

// 执行第一段代码
generator.next() // 函数开始执行 第一段代码: 1
// 执行第二段代码
generator.next() // 第二段代码: 2
// 执行第三段代码
generator.next() // 函数执行结束

next 方法传递参数

function* foo(initial) {
  console.log('函数开始执行')
  const value1 = yield initial + 1
  const value2 = yield value1 + 2
  const value3 = yield value2 + 3
  console.log('函数执行结束')
}

// 生成器上的next方法可以传递参数
const generator = foo(0)

const result1 = generator.next()
console.log('result1: ', result1) // result1:  {value: 1, done: false}

// 上一段代码的返回值的 value 作为这一段代码的 next 函数的参数
const result2 = generator.next(result1.value)
console.log('result2: ', result2) // result2:  {value: 3, done: false}

const result3 = generator.next(result2.value)
console.log('result3: ', result3) // result3:  {value: 6, done: true}

generator.next()

return 方法提前终止函数

yield:暂停函数的执行

return:终止函数的执行

function* foo(initial) {
  console.log('函数开始执行')
  const value1 = yield initial + 1
  const value2 = yield value1 + 2
  const value3 = yield value2 + 3
  console.log('函数执行结束')
}

const generator = foo(0)

console.log(generator.next()) // {value: 1, done: false}
// 使用 return 方法,则终止生成器函数的执行
console.log(generator.return('终止')) // {value: '终止', done: true}
console.log(generator.next()) // {value: undefined, done: true}
console.log(generator.next()) // {value: undefined, done: true}

throw 方法抛出异常

throw方法被捕获以后,会附带执行一次next方法。

function* foo() {
  try {
    yield console.log('第一段')
  } catch (e) {
    console.log('捕获错误:', e)
  }
  yield console.log('第二段')
  yield console.log('第三段')
}

const generator = foo()
generator.next() // 第一段
generator.throw('错误') // 捕获错误:错误  第二段
generator.next() // 第三段

生成器替代迭代器

在迭代器那节,通过 return 一个带有 next 方法的对象创建迭代器

const array = [1, 2, 3]

// 创建数组迭代器的函数
function* createArrayIterator(array) {
  // 1.第一种写法,function 不加 *
  // let index = 0
  // return {
  //   next: function () {
  //     if (index < arr.length) {
  //       return { value: arr[index++], done: false }
  //     } else {
  //       return { value: undefined, done: true }
  //     }
  //   },
  // }

  // 2.第二种写法
  // for (const item of arr) {
  //   yield item
  // }

  // 3.最简洁写法,function 需要加 *
  yield* array
}

const arrayIterator = createArrayIterator(array)
console.log(arrayIterator.next()) // { value: 1, done: false }
console.log(arrayIterator.next()) // { value: 2, done: false }
console.log(arrayIterator.next()) // { value: 3, done: false }
console.log(arrayIterator.next()) // { value: undefined, done: true }
console.log(arrayIterator.next()) // { value: undefined, done: true }

迭代指定范围的函数:

// 创建一个迭代指定范围的函数
function* createRangeIterator(start, end) {
  let index = start
  while (index < end) {
    yield index++
  }
}

const rangeIterator = createRangeIterator(1, 10)
console.log(rangeIterator.next()) // { value: 1, done: false }
console.log(rangeIterator.next()) // { value: 2, done: false }
console.log(rangeIterator.next()) // { value: 3, done: false }
console.log(rangeIterator.next()) // { value: 4, done: false }
console.log(rangeIterator.next()) // { value: 5, done: false }

自定义类的迭代——生成器实现:

// 定义一个班级类, 创建出来的对象是可迭代对象
class Class {
  constructor(classNum, students) {
    this.classNum = classNum
    this.students = students
  }

  addStudent(newStudent) {
    this.students.push(newStudent)
  }

  // [Symbol.iterator]() {
  //   let index = 0
  //   return {
  //     next: () => {
  //       if (index < this.students.length) {
  //         return { value: this.students[index++], done: false }
  //       } else {
  //         return { value: undefined, done: true }
  //       }
  //     },
  //     return: () => {
  //       console.log('迭代器提前终止')
  //       return { value: undefined, done: true }
  //     },
  //   }
  // }
  
  *[Symbol.iterator]() {
    yield* this.students
  }
}

const class1 = new Class('1班', ['小明', '小红', '小王'])
class1.addStudent('小刚')

for (const stu of class1) {
  console.log(stu)
}

async、await 的由来

async、await 是 Promise 和生成器的语法糖

function requestData(num) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * 2)
    }, 1000)
  })
}
// 上一个请求接口返回的数据作为下一个请求的参数
// 1. 多次回调,依然会出现回调地狱的问题
requestData(1).then((res1) => {
  console.log('res1:', res1) // 1秒后输出 res1: 2

  requestData(res1).then((res2) => {
    console.log('res2:', res2) // 2秒后输出 res2: 4

    requestData(res2).then((res3) => {
      console.log('res3:', res3) // 3秒后输出 res3: 8
    })
  })
})

// 2. Promise 中 then 返回以上一次请求结果为参数的新请求
requestData(1)
  .then((res1) => {
    console.log('res1:', res1) // 1秒后输出 res1: 2
    return requestData(res1)
  })
  .then((res2) => {
    console.log('res2:', res2) // 2秒后输出 res2: 4
    return requestData(res2)
  })
  .then((res3) => {
    console.log('res3:', res3) // 3秒后输出 res3: 8
  })

// 3. Promise + 生成器
function* getDataByGenerator() {
  const res1 = yield requestData(1)
  console.log('res1:', res1) // 1秒后输出 res1: 2
  const res2 = yield requestData(res1)
  console.log('res2:', res2) // 2秒后输出 res2: 4
  const res3 = yield requestData(res2)
  console.log('res3:', res3) // 3秒后输出 res3: 8
}
// 3.1 手动执行生成器
const generator = getDataByGenerator()
generator.next().value.then((res1) => {
  generator.next(res1).value.then((res2) => {
    generator.next(res2).value.then((res3) => {
      generator.next(res3)
    })
  })
})
// 3.2 封装一个自动执行的生成器
function execGenerator(genFn) {
  const generator = genFn()
  function exec(res) {
    const result = generator.next(res)
    if (result.done) {
      return result.value
    }
    result.value.then((res) => {
      exec(res)
    })
  }
  exec()
}
execGenerator(getDataByGenerator)
// 3.3 引入第三方包:co(TJ 写的)
const co = require('co')
co(getDataByGenerator)

// 4. 最优方式:async、await
async function getDataByAsync() {
  const res1 = await requestData(1)
  console.log('res1:', res1) // 1秒后输出 res1: 2
  const res2 = await requestData(res1)
  console.log('res2:', res2) // 2秒后输出 res2: 4
  const res3 = await requestData(res2)
  console.log('res3:', res3) // 3秒后输出 res3: 8
}

getDataByAsync()