1

前面2篇系列文章讲解了迭代器和生成器的最常用,最基础的用法;这篇来讨论迭代器和生成器的一些稍稍高级一点的用法:

1: 给迭代器的next()方法传参
2: 从迭代器中抛出错误
3: 在生成器中使用return语句
4: 委托生成器(组合生成器或者生成器组合?)

1: 给迭代器的next()方法传参
在前面2篇系列文章中我们使用的next()方法都是没有传参的,调用next()会依次返回迭代器里面的值。但是,实际上我们是可以给next()方法传参数的,那在这种情况下我们会得到什么样的结果呢?

function* createIterator() {
    let first = yield 1;
    console.log(`first: ${first}`);
    let second = yield first + 2;
    console.log(`second: ${second}`);
    let third = yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next(0));
console.log(iterator.next(4));
console.log(iterator.next(5));
console.log(iterator.next());

我们得到下面的输出结果:

{value: 1, done: false}
first: 4
{value: 6, done: false}
second: 5
{value: 8, done: false}
{value: undefined, done: true}

在第二次和第三次的next调用中我们分别传入参数4和5,而4和5也分别被赋值给了变量first和second。当我们执行:
iterator.next(4)的时候,生成器内部执行的代码实际上是:

let first = 4; yield 4 + 2; //所以我们得到 {value: 6, done: false}

iterator.next(5)的时候,生成器内部执行的代码实际上是:

let second = 5; yield 5 + 3; //所以我们得到 {value: 8, done: false}

看下面一个图或许能更直观一些:
图片描述

        以上截图来自书 Understanding ECMAScript 6

但是上面的例子中,我们也看到一个有趣的现象,就是我们第一次调用next的时候,是传了参数0的iterator.next(0),但是我们依然得到了结果{value: 1, done: false}。这是因为第一次调用next(),无论传递什么参数总是会被丢弃,所以给第一次调用的next()方法传值是无意义的。
或许你看到这里也还不是完全明白了给next()传参时,生成器内部到底是怎样工作。接下来我们再看一个例子,这一次我们在第3次调用next()的时候,不传参数,看会发生什么:

function* createIterator() {
    let first = yield 1;
    console.log(`first: ${first}`);
    let second = yield first + 2;
    console.log(`second: ${second}`);
    let third = yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next(0));
console.log(iterator.next(4));
console.log(iterator.next());
console.log(iterator.next());

我们得到的输出结果是:

{value: 1, done: false}
first: 4
{value: 6, done: false}
second: undefined
{value: NaN, done: false}
{value: undefined, done: true}

在第三次的next调用中,我们并没有传递任何参数,生成器内部的执行情况就是:

let second; yield undefined + 2; //所以我们得到结果{value: NaN, done: false}

其实从这里例子我们也可以看出,在生成器内部,yield执行的结果并不会被保存下来赋值给内部的变量,例如这里我们在第三次没有给next()传递参数,那么second的值就是undefined,而不是第二次yield执行结果的value 6。

2: 从迭代器中抛出错误
我们知道一般的函数的执行结果有2种:

1: 返回一个值
2: 抛出一个错误

生成器函数作为一种特殊的函数,但是它本身也是函数,所以它也可以抛出错误。只是它抛出错误的时间与一般函数不同,看一下下面的代码:

function* createIterator() {
    let first = yield 1;
    let second = yield first + 2;
    yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); //{value: 1, done: false}
console.log(iterator.next(4)); //{value: 6, done: false}
console.log(iterator.throw(new Error('Boom'))); //Uncaught Error: Boom

在生成器内部代码执行情况如下图所示:
图片描述

        以上截图来自书 Understanding ECMAScript 6

当我们抛出错误之后,代码就停止了。let second语句并不会被执行到。
但是生成器里面的throw()它会像yield一样,也会返回一个结果。我们可以像一般函数一样catch这个错误,并且之后的代码依然可以得到执行:

function* createIterator() {
    let first = yield 1;
    let second;
    try {
        second = yield first + 2;
    } catch (error) {
        second = 6;
    }
    yield second + 3;
}

let iterator = createIterator();
console.log(iterator.next()); //{value: 1, done: false}
console.log(iterator.next(4)); //{value: 6, done: false}
console.log(iterator.throw(new Error('Boom'))); //{value: 9, done: false}
console.log(iterator.next());//{value: undefined, done: true}

在这个例子中,我们catch了错误之后,给second赋值6,它后面的代码yield second + 3;也依然可以得到执行。
3: 在生成器中使用return语句
生成器也是函数,所以它也可以使用return语句。只是由于生成器本身的特性,其内部的return的行为会和一般函数有些差别。我们先来看两个例子,就能从中窥探一二:
例1:

function* createIterator() {
    yield 1;
    return;
    yield 2;

}
let iterator = createIterator();
console.log(iterator.next()); //{value: 1, done: false}
console.log(iterator.next()); //{value: undefined, done: true}
console.log(iterator.next());//{value: undefined, done: true}

例2:

function* createIterator() {
    yield 1;
    return 2;
    yield 3;

}
let iterator = createIterator();
console.log(iterator.next()); //{value: 1, done: false}
console.log(iterator.next()); //{value: 2, done: true}
console.log(iterator.next()); //{value: undefined, done: true}

上面的例1只是使用了return语句,然后后面没有跟任何值,例2代码就return了一个数字,我们从调用next()的结果可以看到:

1: return语句会终止返回迭代器里面本可以再迭代的值,会把done设置为false
2: return语句如果指定一个值,那么此次结果的value会被赋为此值;如果没有指定,那value为undefined

4: 委托生成器(组合生成器或者生成器组合?)
单个的生成器函数里,yield后面往往跟一些我们常用的数据类型;但是,我们也可以yield 一个生成器函数,这样的操作就叫委托生成器。先看一个代码的例子:

function *createNumberIterator() {
    yield 1;
    yield 2;

}
function *createColorIterator() {
    yield 'red';
    yield 'yellow';

}
function *createCombinedIterator() {
    yield *createNumberIterator();
    yield *createColorIterator();

}
let combinedIterator = createCombinedIterator();
console.log(combinedIterator.next());//{value: 1, done: false}
console.log(combinedIterator.next());//{value: 2, done: false}
console.log(combinedIterator.next());//{value: "red", done: false}
console.log(combinedIterator.next());//{value: "yellow", done: false}
console.log(combinedIterator.next());//{value: undefined, done: true}

上面的这个示例,我们创建两个不同的生成器函数createNumberIterator()和createColorIterator(),之后在createCombinedIterator()函数里通过yield语句调用前面的2个生成器函数,这样createCombinedIterator()就成了一个拥有以上2个生成器的迭代器的生成器,调用*createCombinedIterator()的next(),就跟一般的生成器的next()方法的行为一样。


nanaistaken
583 声望43 粉丝