JavaScript中的迭代器与生成器 | 青训营笔记

134 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

一、什么是迭代器

本质: 一个可以不断从中取值(直到取值结束)的对象。

迭代器从使用者的角度来看,我们无需关心值的计算方式,只需要使用即可。

要求: 满足迭代器协议。

  • 这个对象提供一个next函数
  • next函数的调用结果为返回一个对象 {done:bool, value: any}

1. 基本实现

let index = 0
const bears = ['ice', 'panda', 'grizzly']

let iterator = {
  next() {
    if (index < bears.length) {
      return { done: false, value: bears[index++] }
    }

    return { done: true, value: undefined }
  }
}

console.log(iterator.next()) //{ done: false, value: 'ice' }
console.log(iterator.next()) //{ done: false, value: 'panda' }
console.log(iterator.next()) //{ done: false, value: 'grizzly' }
console.log(iterator.next()) //{ done: true, value: undefined }

上方的iterator对象符合迭代器协议,所以是一个迭代器。

但是代码违背了高内聚的思想 index和 iterator对象从功能上看属于一个整体,但是却使用了全局变量,当迭代器数量多的时候,考虑到复用性,需要进行封装。

2. 封装实现

const bears = ['ice', 'panda', 'grizzly']

function createArrIterator(arr) {
  let index = 0

  let _iterator = {
    next() {
      if (index < arr.length) {
        return { done: false, value: arr[index++] }
      }

      return { done: true, value: undefined }
    }
  }

  return _iterator
}

let iter = createArrIterator(bears)

console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())

上方代码内聚性非常高,尽可能的进行了复用。

二、什么是可迭代对象

首先 迭代器 和 可迭代对象 不是一个东西 但是存在关联。

可迭代对象是是一个符合可迭代对象协议的对象。

可迭代对象协议

  • 实现类[Symbol.iterator]作为对象中的key的方法,且 这个方法返回一个迭代器对

  • for of 的本质就是调用[Symbol.iterator]为key的方法

Js内置可迭代对象 String、Array、Set、NodeList(类数组对象)、Argument(类数组对象)

示例程序

image.png

可迭代对象实现

let info = {
  bears: ['ice', 'panda', 'grizzly'],
  [Symbol.iterator]: function() {
    let index = 0
    let _iterator = {
       //这里一定要箭头函数,或者手动保存上层作用域的this
       next: () => {
        if (index < this.bears.length) {
          return { done: false, value: this.bears[index++] }
        }
  
        return { done: true, value: undefined }
      }
    }

    return _iterator
  }
}

let iter = info[Symbol.iterator]()
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())

//符合可迭代对象协议 就可以利用 for of 遍历
for (let bear of info) {
  console.log(bear)
}
//ice panda grizzly

可迭代对象的应用

  • for of
  • 展开语法
  • 解构语法
  • promise.all(iterable)
  • promise.race(iterable)
  • Array.from(iterable)

自定义类迭代实现

class myInfo {
  constructor(name, age, friends) {
    this.name = name
    this.age = age
    this.friends = friends
  }

  [Symbol.iterator]() {
    let index = 0

    let _iterator = {
      next: () => {
        const friends = this.friends
        if (index < friends.length) {
          return {done: false, value: friends[index++]}
        }

        return {done: true, value: undefined}
      }
    }

    return _iterator
  }
}

const info = new myInfo('ice', 22, ['panda','grizzly'])

for (let bear of info) {
  console.log(bear)
}

//panda
//grizzly

上方代码实现使用 for offriends 进行了迭代

三、生成器函数

1. 生成器认识

生成器是ES6新增的 函数控制 方案,能够控制函数的暂停和继续。

  • 生成器函数定义使用 function*
  • 通过 yield 控制函数的执行
  • 返回一个 GeneratorGenerator 是一个特殊的迭代器
function* bar() {
  console.log('fn run')
}

bar()

上方代码运行后,生成器函数bar没有执行,其实是暂停了,Generator是一个特殊的迭代器,所以有next方法。

function* bar() {
  console.log('fn run')
}

const generator = bar()

console.log(generator.next())
//fn run
//{ value: undefined, done: true }

调用next方法就能进行一次执行。

function* bar() {
  console.log('fn run start')
  yield 100
  console.log('fn run...')
  yield 200
  console.log('fn run end')
  return 300
}

const generator = bar()

//1. 执行到第一个yield,暂停之后,并且把yield的返回值 传入到value中
console.log(generator.next())
//2. 执行到第一个yield,暂停之后,并且把yield的返回值 传入到value中
console.log(generator.next())
//3. 执行剩余代码
console.log(generator.next())

//打印结果:
//fn run start
//{done:false, value: 100}
//fn run...
//{done:false, value: 200}
//fn run end
//{done:true, value: 300}

上方代码是生成器的多次执行。

  • 调用next方法时,代码就会开始执行
  • 执行到yield x 后,就会暂停,等下下一次调用next方法,函数就会继续往下执行,如果没有了yield关键字,最后一次的next返回的 done为true。

2.生成器函数分段传参

生成器函数可以分段执行也可以分段传参。

function* bar(nickName) {
  const str1 = yield nickName
  const str2 = yield str1 + nickName

  return str2 + str1 + nickName
}

const generator = bar('ice')

console.log(generator.next())
console.log(generator.next('panda '))
console.log(generator.next('grizzly '))
console.log(generator.next())

// { value: 'ice', done: false }
// { value: 'panda ice', done: false }
// { value: 'grizzly panda ice', done: true }
// { value: undefined, done: true }

解释

  • yield左侧可以接受参数
  • 参数来源于调用next方传入的实参

注意: 并不是所有的实参都能传入迭代器方法内部。

image.png


image.png

如上方两个图,第一次调用next方法传入的值不会被接收,第一次其接收的值是生成器函数执行传入的值

在生成器函数执行完最后一个yield语句,暂停之后,后方还有代码(比如使用return返回)的时候,再次调用next,其实参也传入不了内部,因为没有yield关键字可以接收参数了。

3. 生成器替代迭代器

因为生成器是一个特殊的迭代器,所以生成器是可以代替迭代器的

let bears = ['ice','panda','grizzly']

function* createArrIterator(bears) {
  for (let bear of bears) {
    yield bear
  }
}

const generator = createArrIterator(bears)

console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

yield*语法糖写法

yield* 依次迭代这个可迭代对象,相当于遍历拿出每一项 yield item(伪代码)

let bears = ['ice','panda','grizzly']

function* createArrIterator(bears) {
  yield* bears
}

const generator = createArrIterator(bears)

console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

• 依次迭代这个可迭代对象,返回每个item值

四、可迭代对象终极(优雅)封装示例

class myInfo {
  constructor(name, age, friends) {
    this.name = name
    this.age = age
    this.friends = friends
  }

  *[Symbol.iterator]() {
    yield* this.friends
  }
}

const info = new myInfo('ice', 22, ['panda','grizzly'])

for (let bear of info) {
  console.log(bear)
}

//panda
//grizzly

本篇文章是为了强化对知识的学习而写的文章。

原作者:一只小ice

原文地址:juejin.cn/post/714168…