找回密码
 立即注册
首页 业界区 安全 Js 中 迭代器 与 生成器

Js 中 迭代器 与 生成器

广性 昨天 21:11
上篇讲了关于手写 Promise ,  从类的设计, 构造函数设计, then 方法实现, catch 方法借助 then 方法实现,  then 方法的核心优化, 包括多次调用, 链式调用等, 最后再补充了一些常用的类方法, 如 all,  allSettled, any 等实现, 整体过程还是比较有难度的, 体现在状态的变更, 代码执行顺序的控制等, 也算是 js 一个重要知识点吧.
本篇则开始学习 js 中的迭代器和生成器, 只有先搞明白这俩兄弟, 才会对后续将 async ... await + Promise 有深刻认知.
认识迭代器

迭代器 (iterator) 是使用户能在容器对象上, 安全, 统一地访问容器元素的方法机制协议, 而无需关系容器内部实现细节
或者说迭代器是一种对象, 它提供了一种按顺序访问集合 (数组, 列表, 链表, 集合, 映射等) 中的每个元素的方法, 而无需让开发者了解其底层实现 (抽象接口). 它通常实现一个 next() 方法, 犯病一个包含两个属性的对象:
  1. {value: 当前值, done: 是否遍历完成 }
复制代码
它本质上是一种设计模式, 在语言中通过接口和协议规范, 在运行时表现为一个具有状态和行为的对象. 可以将它类比为一个地铁的闸机系统:

  • 设计模式:   "一人一票, 顺序通过"
  • 编程规范:  所有地铁站必须刷卡
  • 接口定义:  必须实现 validate(cart) 和 open() 方法
  • 也是对象:  每个地铁站都有一台闸机
  • 数据结构:  不存数据 (乘客),  只控制访问顺序
迭代器的好处或者说应用场景在于:

  • **统一接口: **不论遍历字符串, 数组, Map, 自定义结构等, 都可以用 for...of 或 .next() 来遍历
  • **延迟计算: **迭代器可以"按需" 生成值, 而非一次性生成所有数据 (生成器)
  • 内存提效: 适用于处理大数据流或者无限序列 (斐波那契数列等)
  • 解耦结构:  数据结构和遍历元素拆分, 让遍历元素部分由迭代器实现
js 中的迭代器

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

  • 迭代器协议, 定义了产生一系列值的标准方式
  • 在 js 中标准就是一个特定的 next() 方法
这个 next() 方法需要满足如下的要求:

  • 是一个无参或者仅一个参数的函数, 返回一个应当拥有如下两个属性的对象
  • done (boolean)
  • value
  1. // 迭代器 认识, 也是一个对象
  2. const names = ['youge', 'cj', 'yaya']
  3. // 创建一个迭代器对象, 来访问 names 数组
  4. let index = 0
  5. const namesIterator = {
  6.   next: function() {
  7.     if (index < names.length) {
  8.       return { done: false, value: names[index++] }
  9.     } else {
  10.       return { done: true, value: undefined }
  11.     }
  12.   }
  13. }
  14. // 通过 迭代器的 next() 方法不断去访问数组元素
  15. console.log(namesIterator.next())
  16. console.log(namesIterator.next())
  17. console.log(namesIterator.next())
  18. console.log(namesIterator.next())
  19. console.log(namesIterator.next())
  20. // { done: false, value: 'youge' }
  21. // { done: false, value: 'cj' }
  22. // { done: false, value: 'yaya' }
  23. // { done: true, value: undefined }
  24. // { done: true, value: undefined }
复制代码
继续来封装一个生成迭代器的函数.
  1. // 生成迭代器的函数, 返回一个对象, 包含 next()
  2. // 数组迭代器
  3. function createArrayIterator(arr) {
  4.   let index = 0
  5.   return {
  6.     next: function() {
  7.       if (index < arr.length) {
  8.         return { done: false, value: arr[index++]}
  9.       } else {
  10.         return { done: true, value: undefined }
  11.       }
  12.     }
  13.   }
  14. }
  15. const names = ['youge', 'cj', 'yaya']
  16. const nums = [10, 20, 30]
  17. const namesIterator = createArrayIterator(names)
  18. console.log(namesIterator.next())
  19. console.log(namesIterator.next())
  20. console.log(namesIterator.next())
  21. console.log(namesIterator.next())
  22. // { done: false, value: 'youge' }
  23. // { done: false, value: 'cj' }
  24. // { done: false, value: 'yaya' }
  25. // { done: true, value: undefined }
  26. // { done: true, value: undefined }
  27. // nums数组 的迭代器
  28. const numsIterator = createArrayIterator(nums)
  29. console.log(numsIterator.next())
  30. console.log(numsIterator.next())
  31. console.log(numsIterator.next()) // { done: false, value: 30 }
  32. console.log(numsIterator.next()) // { done: true, value: undefined }
  33. // 创建一个无限的迭代器
  34. function createNumIterator() {
  35.   let index = 0
  36.   return {
  37.     next: function() {
  38.       return { done: false, value: index++ }
  39.     }
  40.   }
  41. }
  42. const numIterator = createNumIterator()
  43. console.log(numIterator.next())  // 0
  44. console.log(numIterator.next())  // 1
  45. console.log(numIterator.next())  // 2
  46. console.log(numIterator.next())  // 3
  47. console.log(numIterator.next())  // 4
复制代码
可迭代对象

上面认识迭代器的时候, 这个代码分为了 3个部分:

  • 创建将要被迭代的数组 (全局)
  • 创建一个对象, 里面实现了一个叫 next() 的方法来获取数组数据 (全局)
  • 创建一个全局变量 index 来控制 对象 里面的 next 方法, 来访问数组对象
现在要来思考的是, 如何将上面这有紧密联系但是有互相分开的3部分组合到一起, 即统一放在同一个对象中.
  1. // 组合到一起
  2. const newObj {
  3.   names;
  4.   index;
  5.   obj.next();
  6. }
复制代码
这个能组合到一起的对象, 就称为 可迭代对象

  • 该对象实现了 iterable protocol  可迭代协议
  • 该协议必须实现 @@iterator方法,  对应在代码中实现 [Symbol.iterator] 方法即可
  1. const iterableObje= { [Symbol.iterator]: function() { return 迭代器 } }
复制代码
迭代器
是一个对象, 它实现了 迭代器协议, 表现为该对象有一个 .next() 方法, 并返回 { value, done }  的结构
可迭代对象
也是一个对象, 它实现了 可迭代协议, 表现为该对象有一个 Symbol.ierator 方法, 该方法返回一个 实现了迭代器协议的对象
  1. // 可迭代对象 与 迭代器
  2. const arr = [1, 2, 3]
  3. console.log(arr[Symbol.iterator]) // 是个函数, arr 是可迭代对象
  4. // 数组也是对象, 内置实现了可迭代协议, 可以调用 Symbol.iterator
  5. // 该方法返回一个迭代器, 可以用它来进行 .next() 获取元素
  6. const iter = arr[Symbol.iterator]()
  7. console.log(iter.next())
  8. console.log(iter.next())
  9. console.log(iter.next())
  10. console.log(iter.next())
  11. // { value: 1, done: false }
  12. // { value: 2, done: false }
  13. // { value: 3, done: false }
  14. // { value: undefined, done: true }
复制代码
对比项可迭代对象(Iterable)迭代器(Iterator)本质对象对象核心特征有 [Symbol.iterator]() 方法有 .next() 方法协议实现 可迭代协议实现 迭代器协议作用能被 for...of、...、yield* 等消费负责实际遍历,返回 {value, done}是否可遍历✅ 可以被遍历✅ 可以被遍历(因为迭代器自己也是可迭代的)典型例子Array, String, Map, Set, argumentsarray[Symbol.iterator]() 的返回值厘清概念之后, 现对之前的数组, 迭代器, 迭代获取数组元素这个过程进行封装, 即将 nams 数组变为可迭代对象.
  1. // 可迭代对象
  2. const iterableObj = {
  3.   names: ['youge', 'cj', 'yaya'],
  4.   // Symbol.iterator 用 [] 包起来是让其作为字面量对象的 "计算属性"
  5.   [Symbol.iterator]: function() {
  6.     let index = 0
  7.     return {
  8.       next: () => { // 箭头函数无绑定this, 因此 next() 中的 this 会找到父级的 names
  9.         if (index < this.names.length) {
  10.           return { done: false, value: this.names[index++]}
  11.         } else {
  12.           return { done: true, value: undefined}
  13.         }
  14.       }
  15.     }
  16.   }
  17. }
  18. // 此时 iterableObj 对象是一个 可迭代对象
  19. console.log(iterableObj[Symbol.iterator]) // 函数
  20. // 第一次调用
  21. const iterator = iterableObj[Symbol.iterator]()
  22. console.log(iterator.next())
  23. console.log(iterator.next())
  24. console.log(iterator.next())
  25. console.log(iterator.next())
  26. // 第二次调用
  27. const iterator2 = iterableObj[Symbol.iterator]()
  28. console.log(iterator2.next())
  29. // for ... of 可以遍历, 可迭代对象
  30. for (const item of iterableObj) {
  31.   console.log(item) // youge, cj, yaya
  32. }
复制代码
注意点:

  • Symbol.iterator 用 "[ ]" 包起来是让其作为字面量对象的 "计算属性"
  • 利用箭头函数无绑定 this, 因此 next() 中的 this 会找到父级的 names
  • for ... of 可以遍历, 可迭代对象
可见在 js 中的这个 this 的理解是非常重要的, 这里就用到了隐式调用原则嘛.
然后也解决了困扰我很久的问题:   js 普通对象为啥不能用 for..of 遍历?  因为普通对象没有实现可迭代协议呀.
  1. // 普通对象不可用 for...of 迭代
  2. const obj = {
  3.   name: 'youge',
  4.   age: 18
  5. }
  6. for (const item of obj) {
  7.   console.log(item)
  8. }
  9. // TypeError: obj is not iterable
复制代码
可以理解这个 for...of 也是一个语法糖, 它做的事情就是用迭代器不断调用 .next() 获取元素, 直到 done:true 的时候, 则停止.
内置迭代器对象

像我们常用的 String, Array, Map, Set, arguments,  NodeList 等原生对象, 都已实现了可迭代协议, 通过它们所产生的对象, 都是一个可迭代对象.
  1. // 内置了`代器的对象
  2. // 字符串
  3. const str = "ab c"
  4. for (const s of str) {
  5.   console.log(s) // a, b, " "
  6. }
  7. // 数组
  8. const arr = [1, 2]
  9. const arrIter = arr[Symbol.iterator]()
  10. console.log(arrIter.next()) // { value: 1, done: false }
  11. console.log(arrIter.next()) // { value: 2, done: false }
  12. console.log(arrIter.next()) // { value: undefined, done: true}
  13. // set / map
  14. const set = new Set()
  15. set.add(10)
  16. set.add(20)
  17. console.log(set[Symbol.iterator]) // [Function: values]
  18. for (const item of set) {
  19.   console.log(item) // 10, 20
  20. }
  21. // map
  22. const map = new Map()
  23. map.set('name', 'youge')
  24. map.set(() => {}, 'cjj')
  25. for (const item of map) {
  26.   console.log(item)
  27.   // [ 'name', 'youge' ]
  28.   // [ [Function (anonymous)], 'cjj' ]
  29. }
  30. // 函数中的 arguments
  31. function foo() {
  32.   for (const arg of arguments) {
  33.     console.log(arg) // 1, 2, 3
  34.   }
  35. }
  36. foo(1, 2, 3)
复制代码
这些内置对象, 都是可迭代对象, 原因就是它们都实现了 Symbol.iterable 方法, 它会返回迭代器, 内部实现元素的遍历啦.
可迭代对象的应用场景


  • js 语法提升:  for ...of;   展开表达式;   yield;  解构赋值等
  • 创建对象时:  new Mapt([iterable]);  new Set([iterable]), new WeakMap([iterable])  等
  • 方法调用时:  Promise.all(iterable);  Promise.all(iterable);   Array.from(iterable) 等
  1. // 可迭代对象应用
  2. // 1. for...of 语法支持
  3. const arr = [1, 2, 3]
  4. for (const item of arr) {
  5.   console.log(item) // 1, 2, 3
  6. }
  7. // 2. 展开运算符
  8. const iterableObj = {
  9.   names: ['youge', 'cj', 'yaya'],
  10.   [Symbol.iterator]: function() {
  11.     let index = 0
  12.     return {
  13.       next: () => {
  14.         if (index < this.names.length) {
  15.           return { done: false, value: this.names[index++] }
  16.         } else {
  17.           return { done: true, value: undefined }
  18.         }
  19.       }
  20.     }
  21.   }
  22. }
  23. const names = ['bob', 'jack']
  24. const newNames = [...names, ...iterableObj]
  25. // [ 'bob', 'jack', 'youge', 'cj', 'yaya' ]
  26. console.log(newNames)
  27. // ES9 新增普通对象特性, 虽不可迭代, 但要求支持展开运算符
  28. // 猜测实现原理 for (const entry obj.entries ) {} 组装 key, value 就搞定了
  29. const obj = { name: 'youge', age: 18 }
  30. console.log({...obj}) // { name: 'youge', age: 18 }
  31. // 3. 解构语法
  32. const [name1, name2] = names
  33. console.log(name1, name2) // bob, jack
  34. const { name, age } = obj
  35. console.log(name, age) // youge, 18
  36. // 4. 创建其他对象时
  37. const set1 = new Set(iterableObj)
  38. const set2 = new Set(arr)
  39. // 可迭代对象, 转为数组, 如 arguments
  40. const arr1 = Array.from(iterableObj)
  41. // 5. Promise.all(), 传普通值会自动 Promise.resolve(普通值)
  42. Promise.all(iterableObj).then(res => {
  43.   console.log("res: ", res) // res:  [ 'youge', 'cj', 'yaya' ]
  44. })
复制代码
自定义类的可迭代实现

上述通过 Array, Set, Map, String 等类, 创建出来的对象都是可迭代对象.
现在要来实现一个自己的类, 使其创建出来的对象也是可迭代的, 即在类中实现 [Symbol.iterator] 方法即可
用一个案例:  创建一个 Classroom 的类:

  • 每个教室有自己的编号, 楼层, 当前教室的学生
  • 教室可以新进来学生 (push)
  • 创建的教室对象是可迭代对象
  1. // 自定义类 - 实现可迭代功能
  2. class Classroom {
  3.   constructor(id, floor, students) {
  4.     this.id = id
  5.     this.floor = floor
  6.     this.students = students
  7.   }
  8.   entry(newStudent) {
  9.     this.students.push(newStudent)
  10.   }
  11. }
  12. const classroom = new Classroom("301", "3楼", ['bob', 'jack'])
  13. classroom.entry('cj')
  14. // 需求: 将教室对象都迭代出来
  15. for (const item of classroom) {
  16.   console.log(item)
  17. }
复制代码
当前是不能使用 for...of 语法的, 因为我们这个自定义的类, 没有实现可迭代协议. 则我们在对象方法上去实现这个 Symbol.iterator 的方法即可.
  1. // 自定义类 - 实现可迭代功能
  2. class Classroom {
  3.   constructor(id, floor, students) {
  4.     this.id = id
  5.     this.floor = floor
  6.     this.students = students
  7.   }
  8.   entry(newStudent) {
  9.     this.students.push(newStudent)
  10.   }
  11.   // 让对象可迭代, 添加 [Symbol.iterator] 方法
  12.   [Symbol.iterator]() {
  13.     let index = 0
  14.     return {
  15.       next: () =>  {
  16.         if (index < this.students.length) {
  17.           return { done: false, value: this.students[index++] }
  18.         } else {
  19.           return { done: true, value: undefined }
  20.         }
  21.       }
  22.     }
  23.   }
  24. }
  25. const classroom = new Classroom("301", "3楼", ['bob', 'jack'])
  26. classroom.entry('cj')
  27. // 需求: 将教室内学生都迭代出来
  28. for (const item of classroom) {
  29.   console.log(item) // bob, jack, cj
  30. }
复制代码
生成器

生成器 (Generator) 是一种特殊的函数,  它可以在函数执行中暂停, 之后再从暂停的地方恢复执行.
它返回一个生成器对象, 该对象既是迭代器, 也是可迭代对象
它提供了一种暂停和恢复函数执行的能力, 是实现惰性求值, 异步编程, 迭代器 等高级功能的核心工具.
  1. // 普通函数的执行, 不能暂停
  2. function foo() {
  3.   console.log(100)
  4.   console.log(200)
  5.   console.log(300)
  6. }
  7. foo()
  8. // 需求: 在 打印 200 时先暂停, 晚点再往后执行
复制代码
普通函数不能控制 "中途暂停", 此功能在当前知识体系下, 是不能实现的.
生成器函数

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

  • 需要在 function 的后面加一个符号 *
  • 可以通过 yield 关键字来控制函数执行流程
  • 返回值是一个 生成器对象,  它是一种特殊的迭代器
  1. // 生成器函数
  2. function* foo() {
  3.   console.log('函数开始执行...')
  4.   console.log('第一段代码: ', 100)
  5.   yield
  6.   console.log("第二代代码: ", 200)
  7.   yield
  8.   console.log("第三段代码: ", 300)
  9.   yield
  10.   console.log('函数执行结束')
  11. }
  12. // 调用 生成器函数时, 会返回一个生成器对象, 特殊迭代器
  13. const generator = foo()
  14. // 执行第一段代码, 第一个 yield
  15. generator.next()
  16. // 执行第二段代码
  17. generator.next()
  18. // 执行第三段代码
  19. generator.next()
  20. generator.next() // 函数执行结束
复制代码
即通过 yield 来控制函数的执行顺序, 然后调用生成器函数, 返回的是一个生成器对象, 它是一个特殊的迭代器, 因此可以用它来调用 .next() 方法, 对应于 yield 的分段代码.
  1. // 生成器函数-执行流程
  2. // 当遇到 yield  时, 暂停函数执行
  3. // 当遇到 return 时, 生成器停止执行   
  4. function* foo() {
  5.   console.log('函数开始执行...')
  6.   console.log('第一段代码: ', 100)
  7.   yield 111
  8.   console.log("第二代代码: ", 200)
  9.   yield
  10.   console.log("第三段代码: ", 300)
  11.   yield 333
  12.    
  13.   console.log('函数执行结束')
  14.   return 'over'
  15. }
  16. // 调用 生成器函数时, 会返回一个生成器对象, 特殊迭代器
  17. const generator = foo()
  18. // 生成器对象 的 next() 也有返回值
  19. console.log(generator.next())
  20. console.log(generator.next() )
  21. console.log(generator.next() )
  22. console.log(generator.next() )// 函数执行结束
  23. // 函数开始执行...
  24. // 第一段代码:  100
  25. // { value: 111, done: false }
  26. // 第二代代码:  200
  27. // { value: undefined, done: false }
  28. // 第三段代码:  300
  29. // { value: 333, done: false }
  30. // 函数执行结束
  31. // { value: 'over', done: true }
复制代码
通过 yield 可以有返回值, 对应到 { value, done} 中.  最后可以进行 return, 值作为最后的 value, done 的值变为 true.
生成器传递参数- next()

在调用 next() 函数时, 传递参数, 则此参数会作为上一个 yield 语句的返回值, 然后在下一个yield 间进行运算
也就是说, 我们是为本次的代码块提供了一个参数值进来.
  1. const generator = foo(5)
  2. // 生成器对象 的 next() 也有返回值
  3. // next() 也可以传递参数
  4. console.log(generator.next())
  5. // 给第二个 next() 传参
  6. console.log(generator.next(10) )
复制代码
给第二段代码传参,  则先需要在 第一个段代码的 yield 的地方进行定义和接收,  然后在第二个 yield 后边处理
  1. function* foo() {
  2.   console.log('第一段代码: ', 100)
  3.   const n = yield 100 * num  // 这里接收
  4.   console.log("第二代代码: ", 200)
  5.   const count = yield 200 * n // 这里调用
  6. }
复制代码
同样的做法, 可以给第一个 (很少见) 或者第三个都进行这样的处理:
  1. // 生成器-next() 传递参数
  2. function* foo(num) {
  3.   console.log('函数开始执行...')
  4.   console.log('第一段代码: ', 100)
  5.   const n = yield 100 * num
  6.   console.log("第二代代码: ", 200)
  7.   const count = yield 200 * n
  8.   console.log("第三段代码: ", 300)
  9.   yield 300 * count
  10.    
  11.   console.log('函数执行结束')
  12.   return 'over'
  13. }
  14. const generator = foo(5)
  15. // 生成器对象 的 next() 也有返回值
  16. // next() 也可以传递参数
  17. console.log(generator.next())
  18. console.log(generator.next(10) )
  19. console.log(generator.next(20) )
  20. console.log(generator.next() )
复制代码
  1. 函数开始执行...
  2. 第一段代码:  100
  3. { value: 500, done: false }
  4. 第二代代码:  200
  5. { value: 2000, done: false }
  6. 第三段代码:  300
  7. { value: 6000, done: false }
  8. 函数执行结束
  9. { value: 'over', done: true }
复制代码
生成器传递参数- return()

还有一种传参方式是通过 return() 函数, 它会直接终止生成器, 在当前代码块执行之前.
但这样的话就表示生成器函数的结束, 之后调用 next 则不会再继续生成值了.
  1. // 生成器函数-return 函数
  2. function* foo(num) {
  3.   console.log('函数开始执行...')
  4.   console.log('第一段代码: ', 100)
  5.   const n = yield 100 * num
  6.   console.log("第二代代码: ", 200)
  7.   const count = yield 200 * n
  8.   console.log("第三段代码: ", 300)
  9.   yield 300 * count
  10.    
  11.   console.log('函数执行结束')
  12.   return 'over'
  13. }
  14. const generator = foo(5)
  15. console.log(generator.next())
  16. // 第二段代码, 执行 return
  17. console.log(generator.return(222))
  18. console.log(generator.next(20) )
  19. console.log(generator.next())
复制代码
  1. 函数开始执行...
  2. 第一段代码:  100
  3. { value: 500, done: false }
  4. { value: 222, done: true }
  5. { value: undefined, done: true }
  6. { value: undefined, done: true }
复制代码
则在第二段的时候, 调用 "generator.return(222)"  相当于是在第一段代码执行结束后进行 return
  1. function* foo(num) {
  2.   console.log('函数开始执行...')
  3.   console.log('第一段代码: ', 100)
  4.   const n = yield 100 * num
  5.   
  6.   // 相当于在这里终止了生成器
  7.   return 222
  8.   // 因为 return 了, 则后面的不会再进行 yield 新值了
  9.   console.log("第二代代码: ", 200)
  10.   const count = yield 200 * n
  11.   
  12.   // ....
  13. }
复制代码
生成器抛出异常 - throw()

除了可以给生成器函数内部传参外, 也可以给生成器函数内部抛出异常.

  • 抛异常后, 可以在生成器函数中捕获异常
  • 但在 catch 语句中则不能再 yield 新值了, 但在 catch 语句外用 yield 中断语句执行可以
  1. // 生成器函数-throw() 捕捉异常
  2. function* foo() {
  3.   console.log('函数开始执行...')
  4.   console.log('第一段代码: ', 100)
  5.   try {
  6.     yield 100
  7.   } catch (err) {
  8.     console.log('捕捉到异常: ', err)
  9.   }
  10.   
  11.   console.log("第二代代码: ", 200)
  12.   const count = yield 200
  13.   console.log("第三段代码: ", 300)
  14.   yield 300 * count
  15.    
  16.   console.log('函数执行结束')
  17.   return 'over'
  18. }
  19. const generator = foo()
  20. console.log(generator.next())
  21. // 第二段代码, 执行 throw, 则会在第一段代码执行完后就异常了
  22. console.log(generator.throw('err'))
  23. // 处理完异常后, 后续逻辑则正常运行
  24. console.log(generator.next(20))
  25. console.log(generator.next())
复制代码
  1. 函数开始执行...
  2. 第一段代码:  100
  3. { value: 100, done: false }
  4. 捕捉到异常:  err
  5. 第二代代码:  200
  6. { value: 200, done: false }
  7. 第三段代码:  300
  8. { value: 6000, done: false }
  9. 函数执行结束
  10. { value: 'over', done: true }
复制代码
生成器 替代 迭代器使用

生成器是一种特殊的迭代器, 则在一些情况下可以是使用生成器来代替迭代器的.
  1. // 生成器 代替 迭代器
  2. // 迭代器
  3. function arrayIterator(arr) {
  4.   let index = 0
  5.   return {
  6.     next: function() {
  7.       if (index < arr.length) {
  8.         return {done: false, value: arr[index++]}
  9.       } else {
  10.         return {done: true, value: undefined}
  11.       }
  12.     }
  13.   }
  14. }
  15. const names = ['youge', 'cj', 'yaya']
  16. const namesIterator = arrayIterator(names)
  17. console.log(namesIterator.next())
  18. console.log(namesIterator.next())
  19. console.log(namesIterator.next())
  20. // 也可以用生成器替代
  21. function* arrayGenerator(arr) {
  22.   for (const item of arr) {
  23.     yield item  // {value: xxx, done: false}
  24.   }
  25. }
  26. const namesGenerator = arrayGenerator(names)
  27. console.log(namesGenerator.next())
  28. console.log(namesGenerator.next())
复制代码
我们还可以使用 yield* 来生成一个可迭代对象.
这时候相当于是一个 yield语法糖, 会以此迭代这个可迭代对象, 每次迭代其中的一个值.
  1. // 生成器 代替 迭代器 yield* 语法糖
  2. function* arrayGenerator(arr) {
  3.   // yield* 可迭代对象
  4.   yield*  arr
  5. }
  6. const names = ['youge', 'cj', 'yaya']
  7. const namesGenerator = arrayGenerator(names)
  8. console.log(namesGenerator.next())
  9. console.log(namesGenerator.next())
  10. console.log(namesGenerator.next())
  11. console.log(namesGenerator.next())
复制代码
  1. { value: 'youge', done: false }
  2. { value: 'cj', done: false }
  3. { value: 'yaya', done: false }
  4. { value: undefined, done: true }
复制代码
来个小案例, 创建一个函数, 这个函数可以迭代一个范围内的数字
  1. // 用生成器代替迭代器
  2. function* createRangeIterator(start, end) {
  3.   let index = start
  4.   while (index < end) {
  5.     yield index++
  6.   }
  7. }
  8. const rangeIterator = createRangeIterator(1, 3)
  9. console.log(rangeIterator.next())
  10. console.log(rangeIterator.next())
  11. console.log(rangeIterator.next())
  12. // { value: 1, done: false }
  13. // { value: 2, done: false }
  14. // { value: undefined, done: true }
  15. // { value: undefined, done: true }
复制代码
同样的, 对于之前自定义类的迭代器, 也是可以改写为生成器的.
  1. // 自定义类 - 实现可迭代功能
  2. class Classroom {
  3.   constructor(id, floor, students) {
  4.     this.id = id
  5.     this.floor = floor
  6.     this.students = students
  7.   }
  8.   entry(newStudent) {
  9.     this.students.push(newStudent)
  10.   }
  11.   // 让对象可迭代, 添加 [Symbol.iterator] 方法
  12.   [Symbol.iterator]() {
  13.     let index = 0
  14.     return {
  15.       next: () =>  {
  16.         if (index < this.students.length) {
  17.           return { done: false, value: this.students[index++] }
  18.         } else {
  19.           return { done: true, value: undefined }
  20.         }
  21.       }
  22.     }
  23.   }
  24. }
复制代码
现在可以改成这样的:
  1. // 自定义类 - 迭代逻辑, 用生成器替换
  2. class Classroom {
  3.   constructor(id, floor, students) {
  4.     this.id = id
  5.     this.floor = floor
  6.     this.students = students
  7.   }
  8.   entry(newStudent) {
  9.     this.students.push(newStudent)
  10.   }
  11.   // 用生成器来实现 [Symbol.iterator] 方法
  12.   *[Symbol.iterator]() {
  13.     yield* this.students  // 妙呀!
  14.   }
  15. }
复制代码
  1. // 测试一下
  2. const classroom = new Classroom("301", "3楼", ['bob', 'jack'])
  3. classroom.entry('cj')
  4. // 需求: 将教室对象都迭代出来
  5. for (const item of classroom) {
  6.   console.log(item)
  7. }
复制代码
  1. bob
  2. jack
  3. cj
  4. cj@m
复制代码
在实际应用中, 能用生成器解决问题, 尽量用它, 就不用自己写迭代器的一通逻辑, 直接用这种大道至简的语法糖:
  1. yield* 可迭代对象  // 1行顶10行
复制代码
至此, 关于迭代器和生成器的基本使用部分就到这了. 整体操作难度不大, 但是概念特别多, 比如说迭代器, 迭代器对象, 可迭代对象, 生成器, 生成器函数, 生成器是特殊迭代器, 生成器可以在某些情况下替代迭代器, for ...of 的语法糖, yield* 可迭代对象等. 就还是细节很多, 理解为主, 能用就行.

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册