迭代器和生成器(Iterators and generators)

处理集合中的每个项(the items in a collection)是非常常见的操作。JavaScript提供了许多迭代集合的方法,从简单的for循环到map()和filter()。迭代器和生成器将迭代的概念直接引入到核心语言中,并提供了一种自定义for...of循环行为的机制。

迭代器(Iterators)

在 JavaScript 中,迭代器是一个对象,它定义了一个序列,并在其终止时可能返回一个值。更具体地,迭代器是实现 迭代器协议 的任何对象,其通过next()方法实现迭代器协议,所述next()方法方法返回具有两个属性(value属性和done属性)的对象:value属性是序列中的下一个值;done属性会在序列中的最后一个值被返回时返回true。如果 valuedone 并存,则为迭代器的返回值。一旦创建,迭代器对象就可以通过反复调用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结构中会循环哪些值。一些内置类型,如 ArrayMap 拥有默认的迭代行为,而其他类型(比如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、Mapset都是内置的可迭代对象,因为它们的原型对象都有一个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了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值