JS查漏补缺——迭代器、生成器

94 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情

迭代器

迭代器是一个对象,让我们能够遍历某个数据结构(如:链表或数组)
在JS中,迭代器是一个对象且还需要有next函数(符合迭代器协议)
const iterator = {next: function() {return {}}}

next函数

一个无参数函数,返回一个应当拥有done和value属性的对象:

done (boolean):

  • 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
  • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。

value:迭代器返回的任何 JavaScript 值。done 为 true 时可省略。

// 数组
const names = ["abc", "cba", "nba"]
​
// 创建一个迭代器对象来访问数组
let index = 0
const namesIterator = {
  next: function() {
    if (index < names.length) {
      return { done: false, value: names[index++] }
    } else {
      return { done: true, value: undefined }
    }
  }
}
​
console.log(namesIterator.next())
console.log(namesIterator.next())

可迭代对象

可迭代对象是一个对象且需要实现 @@iterator方法,我们可以使用Symbol.iterator访问该属性(符合可迭代协议)
const iterableObj = {[Symbol.iterator]: function(){return 迭代器}}

const iterableObj = {
  names: ["abc", "cba", "nba"],
  [Symbol.iterator]: function() {
    let index = 0
    return {
      next: () => {  // 使用箭头函数使this指向iterableObj
        if (index < this.names.length) {
          return { done: false, value: this.names[index++] }
        } else {
          return { done: true, value: undefined }
        }
      }
    }
  }
}

用于可迭代对象的语法:

  1. for ...of 可以遍历的东西必须是一个可迭代对象
  2. 展开运算符 ( ... ) [ 在ES9之后新增的特性:普通对象也可以使用展开运算符 ]
  3. 解构赋值 [ 在ES9之后新增的特性:普通对象也可以使用解构赋值]
  4. yield*
  5. 使用Set 、Array.from 创建对象时需要传入可迭代对象

内置可迭代对象

String、Array、TypedArray、Map 和 Set 都是内置可迭代对象,因为它们的原型对象都拥有一个 Symbol.iterator 方法

// 以数组为例:
const names = ["abc", "cba", "nba"]
console.log(names[Symbol.iterator])

生成器

与函数相关,是ES6新增的一种函数控制、使用的方案(控制函数什么时候继续执行、暂停执行等操作) 生成器函数返回值是一个生成器

生成器函数

生成器函数特点:

  • 生成器函数需要在function的后面加一个符号: *
  • 生成器函数可以通过yield关键字来控制函数的执行流程
  • 生成器函数的返回值是一个Generator(生成器)
function* foo() {
  console.log("函数开始执行~")
​
  const value1 = 100
  console.log("第一段代码:", value1)
  yield
​
  const value2 = 200
  console.log("第二段代码:", value2)
  yield
​
  console.log("函数执行结束~")
}
​
// 调用生成器函数时, 会给我们返回一个生成器对象
const generator = foo()
​
// 开始执行第一段代码
generator.next()

我们使用第一个next()调用的时候,执行的是第一个yield上面的代码

  • 当遇到yield时候值暂停函数的执行
  • 当遇到return时候生成器就停止执行
  • 如果想要第一个next返回的结果不是undefined,则在yield之后加上想要返回的值:yield value1

生成器本质上是一个特殊的迭代器

//打印的结果与迭代器的形式是一样的
const generator = foo()
console.log("返回值:", generator.next())
// 返回值: { value: undefined, done: false }

生成器的方法使用

next传递参数

我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值;
而这个传递进来的参数则为下一个代码块执行提供了一个值

function* foo(num) {
  console.log("函数开始执行~")
​
  const value1 = 100 * num
  console.log("第一段代码:", value1)
  const n = yield value1
​
  const value2 = 200 * n
  console.log("第二段代码:", value2)
​
  console.log("函数执行结束~")
  return "123"
}
​
// 生成器上的next方法可以传递参数
//给第一个代码块传参
const generator = foo(5)  // 传入的5被上面的num接收
generator.next()
// 给第二个代码块传参,传入的10对应上面的n
generator.next(10)

return终止执行

// 相当于在代码块的后面加上return, 就会提前终端生成器函数代码继续执行
generator.return(15)

用生成器替代迭代器

// 原先用迭代器实现:
function createArrayIterator(arr) {
  let index = 0
  return {
    next: () => {  
      if (index < arr.length) {
         return { done: false, value: arr[index++] }
      } else {
        return { done: true, value: undefined }
      }
    }
  }
}

因为生成器是特殊的迭代器,所以我们可以用生成器去简化代码:

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

yield*

yield是一种yield的语法糖,可以用它来生产一个可迭代对象
如下面的 yield*arr ,这个代码会依次迭代 arr 这个可迭代对象,每次迭代其中的一个值

function* createArrayIterator(arr) {
  yield* arr
}