JS迭代器与生成器

本文详细介绍了JavaScript中的迭代器、生成器的概念及其用法。迭代器是一个对象,通过Iterator协议定义序列,常见如Array迭代器,也可以表示无限序列。生成器函数简化了迭代器的创建,允许自定义迭代算法并自动维护状态。可迭代对象需要实现@@iterator方法以支持迭代。此外,文章还探讨了专用于可迭代对象的语法、高级生成器特性,包括修改生成器状态和异常处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、迭代器

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。 更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。

一旦创建,迭代器对象可以通过重复调用next()显式地迭代。 迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次。 在产生终止值之后,对next()的额外调用应该继续返回{done:true}。

Javascript中最常见的迭代器是Array迭代器,它只是按顺序返回关联数组中的每个值。 虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。 数组必须完整分配,但迭代器仅在必要时使用,因此可以表示无限大小的序列,例如0和无穷大之间的整数范围。


这是一个可以做到这一点的例子。 它允许创建一个简单的范围迭代器,它定义了从开始(包括)到结束(独占)间隔步长的整数序列。 它的最终返回值是它创建的序列的大小,由变量iterationCount跟踪。

下面给出通过生成函数对象与类对象实现范围迭代器的例子,这是一个可以做到这一点的例子。 它允许创建一个简单的范围迭代器,它定义了从开始(包括)到结束(独占)间隔步长的整数序列。 它的最终返回值是它创建的序列的大小,由变量iterationCount跟踪。

二、生成器

虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。生成器函数提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。 生成器函数使用 function*语法编写。 最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。 通过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。

可以根据需要多次调用该函数,并且每次都返回一个新的Generator,但每个Generator只能迭代一次。

我们现在可以调整上面的例子了。 此代码的行为是相同的,但实现更容易编写和读取。

function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
    for (let i = start; i < end; i += step) {
        yield i;
    }
}
var a = makeRangeIterator(1,10,2)
a.next() // {value: 1, done: false}
a.next() // {value: 3, done: false}
a.next() // {value: 5, done: false}
a.next() // {value: 7, done: false}
a.next() // {value: 9, done: false}
a.next() // {value: undefined, done: true}

 三、可迭代对象

若一个对象拥有迭代行为,比如在 for...of 中会循环哪些值,那么那个对象便是一个可迭代对象。一些内置类型,如 Array 或 Map 拥有默认的迭代行为,而其他类型(比如Object)则没有。

为了实现可迭代,一个对象必须实现 @@iterator 方法,这意味着这个对象(或其原型链中的任意一个对象)必须具有一个带 Symbol.iterator 键(key)的属性。

可以多次迭代一个迭代器,或者只迭代一次。 程序员应该知道是哪种情况。 只能迭代一次的Iterables(例如Generators)通常从它们的@@iterator方法中返回它本身,其中那些可以多次迭代的方法必须在每次调用@@iterator时返回一个新的迭代器。

我们可以像这样实现自己的可迭代对象:

var myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
}

for (let value of myIterable) {
    console.log(value);
}
// 1
// 2
// 3

// 或者

[...myIterable]; // [1, 2, 3]

四、专用于可迭代对象的语法

一些语句和表达式专用于可迭代对象,例如 for-of 循环,展开语法yield* 和 解构赋值

for (let value of ['a', 'b', 'c']) {
    console.log(value);
}
// "a"
// "b"
// "c"

[...'abc']; // ["a", "b", "c"]

function* gen() {
  yield* ['a', 'b', 'c'];
}

gen().next(); // { value: "a", done: false }

[a, b, c] = new Set(['a', 'b', 'c']);
a; // "a"

五、高级生成器

生成器会按需计算它们的产生值,这使得它们能够有效的表示一个计算成本很高的序列,甚至是如上所示的一个无限序列。

The next() 方法也接受一个参数用于修改生成器内部状态。传递给 next() 的参数值会被yield接收。要注意的是,传给第一个 next() 的值会被忽略。

下面的是斐波那契数列生成器,它使用了 next(x) 来重新启动序列:

function* fibonacci() {
  var fn1 = 0;
  var fn2 = 1;
  while (true) {
    var current = fn1;
    fn1 = fn2;
    fn2 = current + fn1;
    var reset = yield current;
    if (reset) {
        fn1 = 0;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
console.log(sequence.next().value);     // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3
console.log(sequence.next().value);     // 5
console.log(sequence.next().value);     // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2

你可以通过调用其 throw() 方法强制生成器抛出异常,并传递应该抛出的异常值。这个异常将从当前挂起的生成器的上下文中抛出,就好像当前挂起的 yield 是一个 throw value 语句。

如果在抛出的异常处理期间没有遇到 yield,则异常将通过调用 throw() 向上传播,对 next() 的后续调用将导致 done 属性为 true

生成器具有 return(value) 方法,返回给定的值并完成生成器本身。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值