【JS高级】迭代器 Iterator 与生成器 Generator

850 阅读2分钟

1. 迭代器Iterator

1.1 什么是迭代器?

定义:迭代器是帮助我们对某个数据结构进行遍历的对象。

在javascript中,迭代器也是一个具体的对象,这个对象更需要符合迭代器协议

  1. 迭代器协议(iterator protocol):

    • 迭代器协议定义了产生一系列值的标准方式

    • 在js中这个标准就是一个特定的next方法;

  2. next方法:

    • 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象
      • done: 如果迭代器可以产生序列中的下一个值则为false;如果已经将序列迭代完毕,则为true;
      • value: 迭代器返回的任何值,done为true时可省略;

示例1:一个简单的迭代器

const iterator = {
  next: function() {
    return { done: true, value: 123}
  }
}

上面的iterator就是一个最简单的迭代器,但是我们知道迭代器是帮助我们遍历某个数据结构的,所以这个迭代器看起来是没什么用的。

示例2: 一个能遍历数组的迭代器

const names = ['zhangsan', 'lisi', 'wangwu']

let index = 0
const namesIterator = {
  next: function() {
    if(index < names.length) {
      return { done: false, value: names[index++] }
    } else {
      return { done: true, value: undefined}
    }
  }
}

上面创建了一个namesIterator迭代器,能对names数组进行迭代遍历,使用方法是一次调用next方法

console.log(namesIterator.next()) // { done: false, vlaue: 'zhangsan' }
console.log(namesIterator.next()) // { done: false, vlaue: 'lisi' }
console.log(namesIterator.next()) // { done: false, vlaue: 'wangwu' }
console.log(namesIterator.next()) // { done: true, value: undefined }
console.log(namesIterator.next()) // { done: true, value: undefined }

到这,我们其实应该对迭代器的定义有了初步的认识了。

  • 但是上面的代码整体来说看起来有点奇怪:
    • 获取数组的时候,需要创建一个Index变量,再创建一个所谓的迭代器对象;
    • 事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象;

1.2 什么是可迭代对象?

  • 可迭代对象:当一个对象实现了可迭代协议(iterable protocol)就是一个可迭代对象 注意不要和迭代器协议(iterator protocol)搞混了

  • 可迭代协议:要求对象必须实现@@iterator方法,在代码中我们使用Symbol.iterator访问该属性;并且要求这个函数返回一个迭代器

示例3:将示例2转换成一个可迭代对象

const iterableObj = {
  names: ['zhangsan', 'lisi', 'wangwu'],
  [Symbol.iterator]: function() {
    let index = 0
    return {
      next: () => { // 一定要是箭头函数,this指向iterableObj才能访问到names属性
        if(index < this.names.length) {
          return { done: false, value: this.names[index++] }
        } else {
          return { done: true, value: undefined}
        }
      }
    }
  }
}

上面的iteratorObj就是一个可迭代对象 ,因为它有一个Symbol.iterator属性, 并且返回一个迭代器

使用

const iterator = iterableObj[Symbol.iterator]() // 调用这个函数返回一个迭代器
console.log(iterator.next()) // { done: false, vlaue: 'zhangsan' }
console.log(iterator.next()) // { done: false, vlaue: 'lisi' }
console.log(iterator.next()) // { done: false, vlaue: 'wangwu' }
console.log(iterator.next()) // { done: true, value: undefined }
console.log(iterator.next()) // { done: true, value: undefined }

1.3 可迭代对象的应用

1.3.1 讲一下for...of..

  • for...of语句可以看成是一种语法糖,这种语法糖的作用是循环调用可迭代对象自定义的迭代器的next方法,当done时false的时候返回value,done是true的时候循环停止。

  • for...in语句是遍历一个对象的除Symbol以外的可枚举属性

for...of..可以遍历的东西必须是可迭代对象,之前不懂老是把for...in和for..of...搞混了,会写下面这样的代码

示例4

const obj = {
  name: 'zhangsan',
  age: 20
}

// 输出name age
for(const key in obj) {
  console.log(key)
}

// 遍历obj报错 *TypeError: obj is not iterable* 因为obj不是可迭代对象
for(const key of obj) {
  console.log({key})
}

// 输出zhangsan lisi wangwu,因为iteratorObj是可迭代对象
for(const key of iteratorObj) {
  console.log({key})
}

示例5:arr是一个Array对象,它内部已经实现了 Symbol.iterator方法,所以可以用for...of遍历

const arr = ['zhangsan', 'lisi', 'wangwu']
const iterator = arr[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

javascript内置创建可迭代对象: ArrayMapSetStringTypedArrayarguments

1.3.2 解构操作

  • 对于数组的解构本质是使用迭代器
const [name, age] = ['zhangsan', 20]
  • 对于对象的解构是ES9中新增的特性:用的不是迭代器
const { name } = obj

2. 生成器Generator

2.1 什么是生成器?

定义:生成器是ES6中新增的一种函数控制、使用的方案,它让我们更加灵活的控制函数什么时候执行、暂停执行等。

生成器是和函数结合在一起的,一般我们并不会直接去说生成器,而说生成器函数,因为生成器是由生成器函数生成的

生成器是特殊的迭代器

2.2 什么是生成器函数?

  1. 生成器函数需要在function后面加一个*
  2. 生成器函数通过yield控制代码的执行流程
  3. 生成器函数返回一个生成器

示例6:一个简单的生成器函数foo,执行返回一个生成器generator

function * foo() {
  console.log("step 1")
  yield
  console.log("step 2")
  yield
  console.log("step 3")
}

const generator = foo() // 函数内部不执行 返回一个生成器函数

2.3 生成器函数的执行

  • 可以发现上面执行foo函数,函数执行体根本没有执行,他只是返回了一个生成器对象
    • 那么如何让它执行函数的东西呢,调用next方法即可
    • 生成器是特殊的迭代器,迭代器的next是会有返回值的
    • 可以通过yield来返回结果

示例7: 生成器函数的执行

function * foo() {
  yield 'yield1'
  yield 'yield2'
}

const generator = foo() // 函数内部不执行 返回一个生成器函数

//执行第一个yield并暂停
console.log(generator.next()) // { value: 'yeild 1', done: false }
//执行第二个yield并暂停
console.log(generator.next()) // { value: 'yeild 2', done: false }
//执行第三个yield并暂停
console.log(generator.next()) // { value: undefined, done: true }

2.4 生成器传递参数 – next函数

  • 我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值

示例7:

function * foo() {
 	const next1 = yield 'yeild 1'
  console.log({ next1 }) // { next1: 'next1' }
  const next2 =  yield 'yeild 2'
  console.log({ next2 }) // { next2: 'next2' }
}

const generator = foo() // 函数内部不执行 返回一个生成器函数

//执行第一个yield并暂停
console.log(generator.next()) // { value: 'yeild 1', done: false }
//执行第二个yield并暂停
console.log(generator.next('next1')) // { value: 'yeild 2', done: false }
//执行第三个yield并暂停
console.log(generator.next('next2')) // { value: undefined, done: true }

2.5 生成器替代迭代器

生成器是特殊的迭代器,那么可不可以用来替代迭代器呢,答案是可以的

function createIterator(arr) {
  let index = 0
  return {
    next: () => {
      if (index < arr.length) {
        return { value: arr[index++], done: false}
      } else {
        return { value: undefined, done: true}
      }
    }
  } 
}

const names = ['zhangsan', 'li', 'wangwu']

const iterator = createIterator(names)
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

上面这是一个生成一个迭代器的函数,其实可以用生成器器代替

function * createIterator(arr) {
  for(const item of arr) {
    yield item
  }
}

const names = ['zhangsan', 'li', 'wangwu']

const iterator = createIterator(names)
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

可以再简化一点

  • yield* 后面跟上一个可迭代对象,相当于for..of的语法糖,作用是把可迭代对象arr里面的东西一个一个迭代出来, 每调用一次next方法就yield一个数据
function * createIterator(arr) {
  yield* arr  
}

const names = ['zhangsan', 'li', 'wangwu']

const iterator = createIterator(names)
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())