掌握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

相关文章
|
3月前
|
前端开发 JavaScript 中间件
掌握JavaScript中的迭代器和生成器(下)
掌握JavaScript中的迭代器和生成器(下)
|
8月前
|
JavaScript 前端开发 API
深入解析JavaScript Generator 生成器的概念及应用场景
本文讲解了JS生成器的概念和应用场景。生成器是一个可以暂停和恢复执行的函数。利用生成器我们可以很方便地实现自定义的可迭代对象、状态机、惰性计算等,并且还能用它来简化我们的异步操作代码。
229 0
|
4月前
|
前端开发 JavaScript
使用 JavaScript 和 CSS 的随机颜色生成器
使用 JavaScript 和 CSS 的随机颜色生成器
69 0
|
4月前
|
前端开发 JavaScript 数据安全/隐私保护
使用 HTML、CSS 和 JavaScript 制作的随机密码生成器
使用 HTML、CSS 和 JavaScript 制作的随机密码生成器
85 0
|
6月前
|
JavaScript 前端开发
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(1)
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(1)
|
6月前
|
JavaScript 前端开发
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(2)
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(2)
|
9月前
|
JavaScript 前端开发
《现代Javascript高级教程》Iterator迭代器:简化集合遍历的利器
Iterator 迭代器:简化集合遍历的利器 引言 在 JavaScript 中,迭代器(Iterator)是一种用于遍历集合的接口。迭代器提供了一种统一的方式来访问集合中的元素,无论集合的类型和内部结构如何。通过使用迭代器,我们可以轻松地遍历数组、对象、Map、Set 等各种数据结构,并进行相应的操作。本文将详细介绍迭代器的概念、属性、应用场景,并提供相关的代码示例。
70 1
|
9月前
|
人工智能 前端开发 JavaScript
JavaScript自定义迭代器和提前终止迭代器
与 Iterable 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用。下面这个例子中 的 Counter 类只能被迭代一定的次数
69 0
|
9月前
|
前端开发 JavaScript IDE
JavaScript的文档生成器
大多数 IDE 包含主语言的文档生成器。因为 JavaScript 没有官方 IDE,所以过去文档要么手动生成, 要么借用其他语言的文档生成器生成。不过,目前已出现了一些面向 JavaScript 的文档生成器。
196 0
|
前端开发 JavaScript 容器
JavaScript 中的生成器函数
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器对象。在生成器函数内部可以通过yield关键字来中断代码的执行,迭代器每次执行都会执行到下一次 yield 或者 return。
74 0