迭代器与生成器

迭代器

在JavaScript中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。准确说,迭代器是使用next方法实现iterator protocol的任何一个对象,该方法返回具有两个属性的对象:valuedonevalue是序列中的next的值。done用来判断是否迭代完成,如果已经迭代到序列中的最后一个值,则为true。如果valuedone一起存在,则是迭代器的返回值。

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

JavaScript中最常见的迭代器是数组迭代器,它只是按顺序返回关联数组中的每个值。数组必须完整分配,但迭代器仅在必要时使用。因此,迭代器可以表示大小不受限制的序列,例如0到Infinity之间的整数范围。例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function makeRangeIterator(start = 0, end = Infinite, step = 1) {
let nextIndex = start;
let iterationCount = 0;

const rangeIterator = {
next: function() {
let result;
if(nextIndex < end) {
result = {value: nextIndex, done: false}
nextIndex += step;
iterationCount++;
return result;
}
return { value: iterationCount, done: true }
}
};
return rangeIterator;
}

// 使用
let it = makeRangeIterator(1, 10, 2);
let result = it.next();
while(!result.done) {
console.log(result.value);
result = it.next();
}

生成器函数

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

1
2
3
4
5
6
7
8
9
10
11
12
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: uundefined, done: true}

可迭代对象

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

为了实现可迭代,对象必须实现@@iterator方法,这意味着这个对象(或其原型链中的任何一个对象)必须具有一个带Symbol.iterator键的属性。可以一次或多次迭代一个迭代器,只能迭代一次的iterables通常从他们的@@iterator方法中返回自身。可以多次迭代的方法必须在每次调用@@iterator时返回一个新的迭代器。

自定义的可迭代对象
1
2
3
4
5
6
7
8
9
10
11
12
var myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for(let value of myIterable) {
console.log(value);
}
// 或
[...myIterable]; .. [1, 2, 3]
内置可迭代对象
  • String
  • Array
  • TypedArray
  • Map
  • Set
用于可迭代对象的语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// for...of循环
for(let vaule of ['a', 'b', 'c']) {
console.log(value);
}

// 扩展运算符
[...'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
高级生成器

生成器会按需计算它们的产生值,这使得它们能够有效表示一个计算成本很高的序列。next()方法也接受一个参数用于修改生成器内部状态。传递给next()的参数值会被yield接受,传给第一个next()的值会被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function* fibonacci() {
var fn1 = 0;
var fn2 = 1;
while(true) {
var current = fn1;
fn1 = fn2;
fn2 = current + fn1;
var reset = yield current;
if(reset) {
fn = 0;
fn = 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(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)方法,返回给定的值并完成生成器本身。

本文参考资料

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • © 2019-2021 musi

请我喝杯咖啡吧~

支付宝
微信