处理集合中的每个项(the items in a collection)是非常常见的操作。JavaScript提供了许多迭代集合的方法,从简单的for循环到map()和filter()。迭代器和生成器将迭代的概念直接引入到核心语言中,并提供了一种自定义for...of
循环行为的机制。
迭代器(Iterators)
在 JavaScript 中,迭代器是一个对象,它定义了一个序列,并在其终止时可能返回一个值。更具体地,迭代器是实现 迭代器协议 的任何对象,其通过next()
方法实现迭代器协议,所述next()
方法方法返回具有两个属性(value
属性和done
属性)的对象:value属性是序列中的下一个值;done属性会在序列中的最后一个值被返回时返回true。如果 value
和 done
并存,则为迭代器的返回值。一旦创建,迭代器对象就可以通过反复调用Next()显式地迭代。迭代器上的迭代被称为消耗迭代器,因为它通常只能执行一次。在产生了终止值之后,对Next()的额外调用应该继续返回{done:true}
。
Javascript中最常见的迭代器是Array迭代器,它只是按顺序返回关联数组中的每个值。虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。数组必须全部分配,但迭代器只能在必要时使用,因此可以表示无限大小的序列,例如0和无限之间的整数范围。(Arrays must be allocated in their entirety, but iterators are consumed only as necessary and thus can express sequences of unlimited size, such as the range of integers between 0 and Infinity.)
下面的例子,允许创建一个简单的范围迭代器,它定义了从开始到结束 间隔步长 的整数序列。 它的最终返回值是它创建的序列的大小,由变量iterationCount
跟踪:
//自己造的一个迭代器
function makeRangeIterator(start = 0, end = Infinity, 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, 1);
let result = it.next();
console.log(result);
while (!result.done) {
console.log(result.value);
result = it.next();
}
console.log(`Iterated over sequence of size:${result.value}`);
生成器函数(Generator functions)
虽然自己亲手定制的迭代器是有用的工具,但是它们的创建需要仔细的编程,因为需要显式地维护它们的内部状态。
而生成器函数提供了一个强大的替代方法:
它们允许您编写一个执行不连续的单个函数来定义迭代算法。使用 function*
语法编写生成器函数。最初调用时,生成器函数不执行任何代码,而是返回迭代器类型的生成器(Generator)。当通过调用生成器的next()
方法来消耗值时,Generator函数将执行直到遇到yield
关键字。
可以根据需要多次调用该函数并每次调用时返回一个新的生成器,每个生成器只能迭代一次。
修改上面例子的代码,代码修改后的行为是相同的,但是实现起来更容易读写:
//用生成器函数修改上面的例子
function* makeRangeIterator(start=0,end=Infinity,step=1){
for (let i = start; i < end; i+=step) {
yield i;
}
}
var it = makeRangeIterator(0,10,2);
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
// { value: 0, done: false }
// { value: 2, done: false }
// { value: 4, done: false }
// { value: 6, done: false }
// { value: 8, done: false }
// { value: undefined, done: true }
可迭代对象(Iterables)
如果对象定义了其迭代行为,则该对象是可迭代的。例如在for...of
结构中会循环哪些值。一些内置类型,如 Array
或 Map
拥有默认的迭代行为,而其他类型(比如Object
)则没有。
为了实现可迭代,一个对象必须实现 @@iterator
方法,这意味着这个对象(或其原型链中的任意一个对象)必须具有一个带 Symbol.iterator
键(key)的属性。
有可能对一个可迭代对象进行多次迭代,或只迭代一次。由程序员决定是哪种情况。 只能迭代一次的迭代器(例如Generators)通常从它们的@@iterator
方法中返回它本身,而可以多次迭代的方法必须在每次调用@@iterator
时返回一个新的迭代器。
function* makeIterator() {
yield 1;
yield 2;
}
const it = makeIterator();
for(const itItem of it) {
console.log(itItem);
}
console.log(it[Symbol.iterator]() === it) // true;
// This example show us generator(iterator) is iterable object which have @@iterator method return object (it) itself
// and because that this object (it) can iterate only once
// If we change "it" object's @@iterator method to function/generator which return new iterator/generator this object (it)
// can iterate many times
it[Symbol.iterator] = function* () {
yield 2;
yield 1;
};
自定义的可迭代对象
我们可以实现自己的可迭代对象:
let myIterable = {
*[Symbol.iterator](){
yield 'happy';
yield 'chen';
yield '666';
}
}
for (const value of myIterable) {
console.log(value);
}
console.log([...myIterable]);
内置可迭代对象
String、Array、TypedArray、Map
和set
都是内置的可迭代对象,因为它们的原型对象都有一个Symbol.iterator
方法。
用于可迭代对象的语法
一些语句和表达式专用于可迭代对象,例如 for-of
循环,展开语法,yield*
和 解构赋值。
for (const value of ['菠萝头','西瓜脸','樱桃嘴']) {
console.log(value);
}
// 菠萝头
// 西瓜脸
// 樱桃嘴
let spread = [...'最好的我们'];
console.log(spread);
// [ '最', '好', '的', '我', '们' ]
function* gen(){
yield* ['菠萝头','西瓜脸','樱桃嘴'];
}
let it = gen();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
// { value: '菠萝头', done: false }
// { value: '西瓜脸', done: false }
// { value: '樱桃嘴', done: false }
// { value: undefined, done: true }
高级生成器(Advanced generators)
生成器按需计算其产生的值,这使它们能够高效地表示计算成本很高的序列,甚至如上面所演示的无限序列。
next()
方法还可接受一个参数值,该值可用于修改生成器的内部状态。传递给Next()的值将由yield
接收。注意,传递给第一个next()
的值总是被忽略。
比如:向向生成器中的next()方法传递值。* 注意:第一次调用 next() 没有打印value的值 *
function* gen() {
while(true) {
var value = yield null;
console.log(value);
}
}
var g = gen();
g.next(1);
// "{ value: null, done: false }"
g.next(2);
// 2
// "{ value: null, done: false }"
下面的是斐波那契数列生成器,它使用了 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()
方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象。
生成器(Generator)的原型链上有throw()
方法(Generator.prototype.throw())。
语法形式是:gen.throw(exception)
示例:
function* gen(){
while(true){
try{
yield 42;
}catch(e){
console.log('error caught!');
}
}
}
let g =gen();
g.next();
g.throw(new Error('something went wrong!')) // error caught!
如果在生成器中没有捕获到异常,则异常将向上传播调用 throw() ,对 next() 的后续调用将导致 done 属性为 true。
生成器具有 return(value)
方法,return() 方法返回给定的值并结束生成器。(Generator.prototype.return()
)
语法形式:gen.return(value)
举例:
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: false }
g.next(); // { value: undefined, done: true }
g.return(); // { value: undefined, done: true }
g.return(1); // { value: 1, done: true }
注意:调用gen.return()
方法后done的属性值就变成了 true
了。