掌握JavaScript中的迭代器和生成器(上)

简介: 掌握JavaScript中的迭代器和生成器

迭代器和生成器是 ES6 中引入的特性。迭代器通过一次消费一个项目列表来提高效率,类似于数据流。生成器是一种能够暂停执行的特殊函数。调用生成器允许以块的形式(一次一个)生成数据,而无需先将其存储在列表中。下面就来深入理解 JavaScript 中的迭代器和生成器,看看它们是如何使用的,又有何妙用!


迭代器


JavaScript 中的迭代器可以分别两种:同步迭代器异步迭代器

1. 同步迭代器

(1)迭代器和可迭代对象

在 JavaScript 中有很多方法可以遍历数据结构。例如,使用 for 循环或使用 while 循环。迭代器具有类似的功能,但有显着差异。

迭代器只需要知道集合中的当前位置,而其他循环则需要预先加载整个集合才能循环遍历它。迭代器使用 next() 方法访问集合中的下一个元素。 但是,为了使用迭代器,值或数据结构应该是可迭代的。 数组、字符串、映射、集合是 JavaScript 中的可迭代对象。普通对象是不可迭代的。

(2)定义迭代器

下面来看看集合不可迭代的场景:

javascript

复制代码

const favouriteMovies = {
  a: '哈利波特',  b: '指环王',  c: '尖峰时刻',  d: '星际穿越',  e: '速度与激情',}

这个对象是不可迭代的。如果使用普通的 for 循环遍历它,就会抛出错误。 随着 ES6 中迭代器的引入,可以将其转换为可迭代对象以便遍历它。 这些称为自定义迭代器。 下面看看如何实现对象的遍历并打印出来:

javascript

复制代码

favouriteMovies[Symbol.iterator] = function() {  const ordered = Object.values(this).sort((a, b) => a - b);  let i = 0;  return {    next: () => ({      done: i >= ordered.length,      value: ordered[i++]    })  }}for (const v of favouriteMovies) {
  console.log(v);}

输出结果如下:

javascript

复制代码

哈利波特指环王尖峰时刻星际穿越速度与激情

这里使用 Symbol.iterator() 来定义迭代器。任何具有 Symbol.iterator 键的结构都是可迭代的。

可迭代对象具有以下行为:

  1. for..of 循环开始时,它首先查找错误。如果未找到,则它会访问方法和定义该方法的对象。
  2. for..of 循环方式迭代该对象。
  3. 使用该输出对象的 next() 方法来获取要返回的下一个值。
  4. 返回的值的格式为 done:boolean, value: any。 返回 done:true 时循环结束。

下面来创建一个 LeapYear 对象,该对象返回范围为 (start, end) 的闰年列表,并在后续闰年之间设置间隔。

javascript

复制代码

class LeapYear {
  constructor(start = 2020, end = 2040, interval = 4) {
    this.start = start;
    this.end = end;
    this.interval = interval;
  }
  [Symbol.iterator]() {
    let nextLeapYear = this.start;
    return {
      next: () => {
        if (nextLeapYear <= this.end) {
          let result = { value: nextLeapYear, done: false };
          nextLeapYear += this.interval;
          return result;
        }
        return { value: undefined, done: true };
      },
    };
  }
}

在上面的代码中,为自定义类型 LeapYear 实现了 Symbol.iterator() 方法。分别在 this.startthis.end 字段中有迭代的起点和终点。 使用 this.interval来跟踪迭代的第一个元素和下一个元素之间的间隔。

现在,可以在自定义类型上调用 for...of 循环,并查看其行为和输出值,就像默认数组类型一样:

javascript

复制代码

let leapYears = new LeapYear();
for (const leapYear of leapYears) {
    console.log(leapYear);
}

输出结果如下:

javascript

复制代码

202020242028203220362040

这里的 LeapYear 通过 Symbol.iterator() 变成了可迭代对象。

在一些情况下,迭代器会比普通迭代更好。 例如,在没有随机访问的有序集合(如数组)中,迭代器的性能会更好,因为它可以直接根据当前位置检索元素。但是,对于无序集合,由于没有顺序,就不会体验到性能上的重大差异。

使用普通循环算法,例如 for 循环或 while 循环,您只能循环遍历允许迭代的集合:

javascript

复制代码

const favourtieMovies = [
  '哈利波特',
  '指环王',
  '尖峰时刻', 
  '星际穿越',
  '速度与激情',
];
for (let i=0; i < favourtieMovies.length; i++) {
  console.log(favouriteMovies[i]);
}
let i = 0;
while (i < favourtieMovies.length) {
  console.log(favourtieMovies[i]);
  i++;
}

由于数组是可迭代的,因此可以使用 for 循环遍历。 我们也可以为上面实现一个迭代器,这将允许更好地访问基于当前位置的元素,而无需加载整个集合。代码如下:

javascript

复制代码

const iterator = favourtieMovies[Symbol.iterator]();
iterator.next();  // { value: '哈利波特', done: false }
iterator.next();  // { value: '指环王', done: false }
iterator.next();  // { value: '尖峰时刻', done: false }
iterator.next();  // { value: '星际穿越', done: false }
iterator.next();  // { value: '速度与激情', done: false }
iterator.next();  // { value: undefined, done: true }

next() 方法将返回迭代器的结果。它包括两个值; 集合中的元素完成状态。 可以看到,当遍历完成后,即使访问数组外的元素,也不会抛出错误。 它只会返回一个具有 undefined 值和完成状态为 true 的对象。

(3)使用场景

那为什么向自定义对象中添加迭代器呢?我们也可以编写自定义函数来遍历对象以完成同样的事情。

实际上,迭代器是一种标准化自定义对象的优雅实现方式,它为自定义数据结构提供了一种在更大的 JS 环境中很好地工作的方法。因此,提供自定义数据结构的库经常会使用迭代器。例如,  Immutable.JS 库就使用迭代器为其自定义对象(如Map)。所以,如果需要为封装良好的自定义数据结构提供原生迭代功能,就考虑使用迭代器。

2. 异步迭代器

JavaScript 中的异步迭代对象是实现 Symbol.asyncIterator 的对象:

javascript

复制代码

const asyncIterable = {
  [Symbol.asyncIterator]: function() {
  }
};

我们可以将一个函数分配给 [Symbol.asyncIterator] 以返回一个迭代器对象。迭代器对象应符合带有 next() 方法的迭代器协议(类似于同步迭代器)。

下面来添加迭代器:

javascript

复制代码

const asyncIterable = {
  [Symbol.asyncIterator]: function() {
    let count = 0;
    return {
      next() {
        count++;
        if (count <= 3) {
          return Promise.resolve({ value: count, done: false });
        }
        return Promise.resolve({ value: count, done: true });
      }
    };
  }
};

这里用 Promise.resolve 包装了返回的对象。下面来执行 next() 方法:

javascript

复制代码

const go = asyncIterable[Symbol.asyncIterator]();

go.next().then(iterator => console.log(iterator.value));go.next().then(iterator => console.log(iterator.value));

输出结果如下:

javascript

复制代码

12

也可以使用 for await...of 来对异步迭代对象进行迭代:

javascript

复制代码

asyncfunctionconsumer() {
  forawait (const asyncIterableElement of asyncIterable) {    console.log(asyncIterableElement);  }}consumer();

异步迭代器和迭代器是异步生成器的基础,后面会介绍异步生成器。

生成器

JavaScript 中的生成器可以分别两种:同步生成器异步生成器

1. 同步生成器

(1)基本概念

生成器是一个可以暂停恢复并可以产生多个值的过程。JavaScript 中的生成器由一个生成器函数组成,它返回一个可迭代 Generator 对象。

生成器是对 JavaScript 的强大补充。它们可以维护状态,提供一种制作迭代器的有效方法,并且能够处理无限数据流,可用于在前端实现无限滚动等。此外,当与 Promises 一起使用时,生成器可以模拟 async/await 功能,这使我们能够以更直接和可读的方式处理异步代码。尽管 async/await 是处理常见、简单的异步用例(例如从 API 获取数据)的一种更普遍的方式,但生成器具有更高级的功能。

生成器函数是返回生成器对象的函数,由 function 关键字后面跟星号 (*) 定义,如下所示:

javascript

复制代码

function* generatorFunction() {}

有时,我们可能会在函数名称旁边看到星号,而不是 function 关键字,例如 function *generatorFunction(),它的工作原理是相同的,但 function* 是一种更广泛接受的语法。

生成器函数也可以在表达式中定义,就像常规函数一样:

javascript

复制代码

const generatorFunction = function* () {}

生成器甚至可以是对象或类的方法

javascript

复制代码

// 生成器作为对象的方法
const generatorObj = {
  *generatorMethod() {},
}
// 生成器作为类的方法
class GeneratorClass {
  *generatorMethod() {}
}

下面的例子都将使用生成器函数声明得语法。

注意:与常规函数不同,生成器不能使用 new 关键字构造,也不能与箭头函数结合使用。

现在我们知道了如何声明生成器函数,下面来看看生成器返回的可迭代生成器对象。

(2)生成器对象

传统的 JavaScript 函数会在遇到return 关键字时返回一个值。 如果省略 return 关键字,函数将隐式返回 undefined

例如,在下面的代码中,我们声明了一个 sum() 函数,它返回一个值,该值是两个整数参数的和:

javascript

复制代码

functionsum(a, b) {
  return a + b}

调用该函数会返回一个值,该值是参数的总和:

javascript

复制代码

const value = sum(5, 6) // 11

而生成器函数不会立即返回值,而是返回一个可迭代的生成器对象。 在下面的例子中,我们声明了一个函数并给它一个单一的返回值,就像一个标准的函数:

javascript

复制代码

function* generatorFunction() {
  return'Hello, Generator!'}

当调用生成器函数时,它将返回生成器对象,我们可以将其分配给一个变量:

javascript

复制代码

const generator = generatorFunction()

如果这是一个常规函数,我们希望生成器为我们提供函数中返回的字符串。 然而,我们实际得到的是一个处于挂起状态的对象。 因此,调用生成器将提供类似于以下内容的输出:

javascript

复制代码

generatorFunction {<suspended>}
  [[GeneratorLocation]]: VM335:1
  [[Prototype]]: Generator
  [[GeneratorState]]: "suspended"
  [[GeneratorFunction]]: ƒ* generatorFunction()
  [[GeneratorReceiver]]: Window

函数返回的生成器对象是一个迭代器。迭代器是一个具有可用的 next() 方法的对象,该方法用于迭代一系列值。 next() 方法返回一个对象,其包含两个属性:

  • value:当前步骤的值;
  • done:布尔值,指示生成器中是否有更多值。

next() 方法必须遵循以下规则:

  • 返回一个带有 done: false 的对象来继续迭代;
  • 返回一个带有 done: true 的对象来停止迭代。

下面就来在生成器上调用 next() 并获取迭代器的当前值和状态:

javascript

复制代码

generator.next()

这将得到以下输出结果:

javascript

复制代码

{value: "Hello, Generator!", done: true}

调用 next() 时的返回值为 Hello, Generator!,并且 done 的状态为 true,因为该值来自关闭迭代器的返回值。 由于迭代器完成,生成器函数的状态将从挂起变为关闭。这时再次调用生成器将输出以下内容:

javascript

复制代码

generatorFunction {}

除此之外,生成器函数也有区别于普通函数的独特特征。下面我们就来了解一下 yield 运算符并看看生成器如何暂停和恢复执行。


掌握JavaScript中的迭代器和生成器(下)https://developer.aliyun.com/article/1411467

相关文章
|
7月前
|
前端开发 JavaScript 中间件
掌握JavaScript中的迭代器和生成器(下)
掌握JavaScript中的迭代器和生成器(下)
|
6月前
|
存储 JavaScript 前端开发
javascript中的生成器和迭代器是什么
JavaScript中的生成器和迭代器是处理集合数据的利器,它们提供了一种遍历和操作元素的统一方式。迭代器是具有`next()`方法的对象,返回包含`value`和`done`属性的对象,用于循环处理集合。生成器函数更进一步,可以在执行过程中暂停并返回值,通过`yield`产生迭代值,适用于生成序列、异步编程和实现状态机等场景。例如,一个生成器可以无限生成斐波那契数列,或者在读取文件时控制异步流程。使用这些工具,代码变得更简洁、高效。
|
3月前
|
JavaScript 前端开发 Python
JavaScript写个.ts视频文件Url生成器,使用了string.padStart
JavaScript写个.ts视频文件Url生成器,使用了string.padStart
|
3月前
|
JavaScript 索引
|
5月前
|
存储 JavaScript 前端开发
JavaScript编码之路【ES6新特性之 Symbol 、Set 、Map、迭代器、生成器】(二)
JavaScript编码之路【ES6新特性之 Symbol 、Set 、Map、迭代器、生成器】(二)
58 1
|
6月前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的计算机网络课程试卷生成器附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的计算机网络课程试卷生成器附带文章和源代码部署视频讲解等
40 2
|
5月前
|
JavaScript 索引
JS的迭代器是啥?精读JS迭代器
JS的迭代器是啥?精读JS迭代器
32 0
|
5月前
|
存储 JavaScript 前端开发
JavaScript编码之路【ES6新特性之 Symbol 、Set 、Map、迭代器、生成器】(一)
JavaScript编码之路【ES6新特性之 Symbol 、Set 、Map、迭代器、生成器】(一)
43 0
|
7月前
|
存储 JavaScript 前端开发
JavaScript中的复杂功能实现:一个动态表单生成器
JavaScript中的复杂功能实现:一个动态表单生成器
|
7月前
|
JavaScript 前端开发
什么是 JavaScript 中的生成器
什么是 JavaScript 中的生成器
35 0