js中的生成器与迭代器

1. 迭代器

预计阅读时间: 4 分钟

1.1 迭代器

"什么是迭代器

使用户可以在容器对象上遍访的对象,使用该接口无需关心对象内部的实现细节

js中,迭代器是一个符合迭代器协议的具体对象

  • 是一个对象
  • 符合迭代器协议
"迭代器协议

迭代器协议 迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式

在js中,这个标准就是实现了一个特定的next方法

  1. 有一个参数或无参数
  2. 必须返回一个包含value和done的对象。done是一个boolean,表示是否迭代完成,value表示当前具体值

一个简单的例子

迭代器示例
1const arr = [1,2]
2let index = 0 
3const arrIterator = {
4  next:function(){
5    if(index<arr.length){
6      return {
7        done:false, // 是否迭代完成
8        value: arr[index++] //当前具体值
9      }
10    }else {
11      return {
12        done:true,
13      }
14    }
15  }
16}
17console.log('arrIterator', arrIterator.next()) // {done: false, value: 1}
18console.log('arrIterator', arrIterator.next()) // {done: false, value: 2}
19console.log('arrIterator', arrIterator.next()) // {done: true}
20console.log('arrIterator', arrIterator.next()) // {done: true}

1.2 迭代器中断

在迭代过程中被return()throw()方法中断,两个方法都应返回实现 IteratorResult 接口的对象

在迭代器中监听中断事件,在迭代器中实现return方法

迭代器中断
1const arr = [1,2,3,4]
2
3let index = 0 
4const arrIterator = {
5  next:function(){
6    if(index<arr.length){ 
7
8      return {
9        done:false,
10        value: arr[index++]
11      }
12
13    }else {
14      return {
15        done:true,
16      }
17    }
18  },
19  return : (value)=>{
20    // value 通常等价于传递的 value
21    console.log('被中断了')
22    return {done:true}
23  },
24  throw : (exception)=>{
25    // exception 通常是一个 Error 实例
26    console.log('被中断了')
27    return {done:true}
28  }
29}

1.3 可迭代对象

"可迭代对象

可迭代对象是一个实现可迭代协议对象

可迭代协议

  1. 必须实现了[Symbol.iterator]函数
  2. [Symbol.iterator]函数必须返回一个迭代当前对象的迭代器
可迭代对象示例
1// 写一个完整的可迭代对象的例子
2const obj = {
3  friends:[1,2,3,4],
4  [Symbol.iterator](){ // 实现迭代器协议
5    let index = 0
6    return { // 返回一个迭代器
7      next:()=>{ // 实现next方法
8        return {done:false,value:this.friends[index++]}
9      }
10    }
11  }
12}
13
14for (const iterator of obj) {
15  console.log('iterator', iterator)
16}
巧妙把迭代器作为可迭代对象
1// 这是一个迭代器,同时也是一个可迭代对象,特别简化的可迭代对象
2const myIterator = {
3  next() {
4    // ...
5  },
6  [Symbol.iterator]() {
7    return this;
8  },
9};
"可迭代对象的特性
  • 使用特定的js语法: for...of操作、展开运算符操作、yield解构赋值
  • 创建对象时作为初始化参数:new Set(iterable)、new Map(iterable)、new weakMap(iterable) 、new weakSet(iterable)
  • 常见的方法如:Promise.all(iterable)、Promise.race(iterable)、promise.any(iterable),Array.from(iterable) 等

特别注意:

在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性,而不是因为普通对象符合可迭代协议!!

所以不是在构建字面量对象的时候,对一个对象使用展开运算符,会报错

普通对象使用展开运算符报错
1const info = { name:'info',age:18} //普通对象,不符合可迭代协议
2
3function foo(...arg){ // 剩余参数
4   console.log('arg', arg)
5}
6
7foo(...info) //报错

1.4.原生可迭代对象

Array、Set、Map、String、arguments对象、NodeList等

2. 生成器

2.1 生成器

"生成器

生成器是一种函数控制使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行暂停执行

"JS生成器

在js中,生成器是指由生成器函数返回的对象,它同时也一个特殊的迭代器,实现了迭代器协议 (即next方法),也是一个可迭代对象,实现可迭代协议

2.2 生成器函数

"生成器函数

生成器函数也是一个函数,但与普通函数有一些区别:

  • 生成器函数需要在function后面加符号: *
  • 生成器函数可以通过yield关键词来控制函数的执行流程(普通函数不可以)
  • 生成器函数的返回值是一个Generator(生成器)--事实上是一种特殊的迭代器

例子

1function* foo(init){
2  console.log('内部代码执行: 1')
3  const res = yield init
4  console.log('内部代码执行: 2')
5  const res2 = yield init+ res
6  console.log('内部代码执行: 3')
7  const res3 = yield init + res2
8  console.log('内部代码执行: 4')
9  const res4 = yield init + res3
10  console.log('内部代码执行: 5')
11  yield init + res4
12  console.log('内部代码执行: 6')
13}
14
15const generator = foo(1)
16console.log('generator.next(1).value', generator.next().value)
17console.log('generator.next(2).value', generator.next(2).value)
18console.log('generator.next(3).value', generator.next(3).value)
19console.log('generator.next(4).value', generator.next(4).value)
20console.log('generator.next(5).value', generator.next(5).value)
21console.log('generator.next(6).value', generator.next(6).value)
22
23//内部代码执行: 1
24//generator.next(1).value 1
25//内部代码执行: 2
26//generator.next(2).value 3
27//内部代码执行: 3
28//generator.next(3).value 4
29//内部代码执行: 4
30//generator.next(4).value 5
31//内部代码执行: 5
32//generator.next(5).value 6
33//内部代码执行: 6
34//generator.next(6).value undefined

2.3 生成器函数的执行流程

第1步 调用生成器函数

  • 返回一个生成器,但不会执行函数内部任何代码

第2步 第一次调用生成器的next方法

  • next可以传递参数,在函数中通过yield前面定义的变量接收。
  • 会执行函数体第一行到第一个yield之间的代码,并可以通过yield返回值,

第3步 后面每次调用next方法

  • 后面每次调用next方法,都会执行上一个yield后面都下一个yield或者return之间的语句

运行终结

  • 函数中return或者生成器调用return方法都可以终止生成器函数的继续执行
  • 生成器的return方法也可以传递参数,也可以使用throw方法传递一个错误来提前终结

2.3使用生成器

"使用生成器

场景:自定义的迭代器完整实现太麻烦了,生成器函数可以简化迭代器的生成

简化自定义迭代器
1const arr = [1,2,34,4]
2function* foo(init,end){
3  for(let i = init;i<end;i++){
4    yield i
5  }
6}
7
8const generator = foo(1,5)
9console.log('generator.next()', generator.next()) // {value: 1}
10console.log('generator.next()', generator.next()) // {value: 2}
11console.log('generator.next()', generator.next()) // {value: 3}
12console.log('generator.next()', generator.next()) // {value: 4}
13console.log('generator.next()', generator.next()) // {done: true}

yield* 语法糖

  • yield* arr arr需要是一个可迭代对象,那么会自动迭代arr返回
1const arr = [1,2,34,4]
2function* foo(){
3  yield* arr
4}
5
6const generator = foo()
7console.log('generator.next()', generator.next()) // {value: 1}
8console.log('generator.next()', generator.next()) // {value: 2}
9console.log('generator.next()', generator.next()) // {value: 34}
10console.log('generator.next()', generator.next()) // {value: 4}
11console.log('generator.next()', generator.next()) // {done: true}

替代原来[Symbol.iterator]函数,快速实现可迭代协议

1const b = {
2  friends:[1,2,3,4],
3  [Symbol.iterator](){
4    let index = 0
5    return {
6      
7      next:()=>{
8        if(index<this.friends.length){
9          return {
10            value:this.friends[index++],
11            done:false
12          }
13        }
14        else {
15          return {
16            done:true
17          }
18        }
19      }
20    }
21  }
22}
23
24for (const iterator of b) {
25  console.log('iterator', iterator)
26}
27
28//改造后
29
30const b = {
31  friends:[1,2,3,4],
32  *[Symbol.iterator](){
33    yield* this.friends
34  }
35}
36
37for (const iterator of b) {
38  console.log('iterator', iterator)
39}