深入生成器函数


在〈简介生成器函数〉中看到的生成器函数,都只使用了yield而没有return, 如果定义生成器函数的时候使用了return会如何?

function* range(start, end) {
    for(let i = start; i < end; i++) {
        yield i;
    }
    return 'return';
}

for循环之后,程序最后使用return试图返回'return'字符串,来看看执行的结果:

> let g = range(1, 3);
undefined
> g.next();
{ value: 1, done: false }
> g.next();
{ value: 2, done: false }
> g.next();
{ value: 'return', done: true }
>

可以看到,生成器最后迭代出来的对象中,value特性是'return',而done特性被设为true。回想一下,对于for...of,在看到done特性后就停止,因此若是在for...of中,并不会迭代出return指定的值。

> let g2 = range(1, 3);
undefined
> for(let i of g2) {
...     console.log(i);
... }
1
2
undefined
>

你可以使用生成器的return方法,要求生成器直接return,例如:

> function* range(start, end) {
...     for(let i = start; i < end; i++) {
.....         yield i;
.....     }
... }
undefined
> let g = range(1, 3);
undefined
> g.next();
{ value: 1, done: false }
> g.return();
{ value: undefined, done: true }
> let g2 = range(1, 3);
undefined
> g2.next();
{ value: 1, done: false }
> g2.return(10);
{ value: 10, done: true }
>

return方法会在先前yield处进行return,此时返回的对象done特性会是truevalue特性会是undefined,如果return方法有指定实参,那么返回的对象done特性会是truevalue特性会是指定的实参。

类似地,生成器有个throw方法,它会在先前yield处,将throw方法指定的值进行throw,例如:

> let g = range(1, 3);
undefined
> g.next();
{ value: 1, done: false }
> g.throw('Orz');
Thrown: Orz
> let g2 = range(1, 3);
undefined
> g2.next();
{ value: 1, done: false }
> g2.throw(new Error('XD'));
Error: XD
    at repl:1:10
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at REPLServer.defaultEval (repl.js:240:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:441:10)
    at emitOne (events.js:121:20)
    at REPLServer.emit (events.js:211:7)
    at REPLServer.Interface._onLine (readline.js:282:10)
    at REPLServer.Interface._line (readline.js:631:8)
>

生成器实现了迭代器的协定,如果你试着检验生成器,会发现它是生成器函数的实例,生成器的原型对象就是生成器函数的prototype

> let g = range(1, 3);
undefined
> g instanceof range;
true
> g.__proto__ == range.prototype;
true
>

话虽如此,你不能对一个生成器函数使用new

> let g2 = new range(1, 3);
TypeError: range is not a constructor
    at repl:1:10
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at REPLServer.defaultEval (repl.js:240:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:441:10)
    at emitOne (events.js:121:20)
    at REPLServer.emit (events.js:211:7)
    at REPLServer.Interface._onLine (readline.js:282:10)
    at REPLServer.Interface._line (readline.js:631:8)
>

rangeprototype上,constructor特性也不是参考range

> range.prototype.constructor === range;
false
>

由于不能将生成器函数当成是构造函数,因此在生成器函数中编写this的意义并不大,因为直接调用函数的话,this会是undefined

> function* range(start, end) {
...     for(this.i = start; this.i < end; this.i++) {
.....         yield this.i;
.....     }
... }
undefined
> let g = range(1, 3);
undefined
> g.next();
TypeError: Cannot set property 'i' of undefined
    at range (repl:2:16)
    at range.next (<anonymous>)
    at repl:1:3
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at REPLServer.defaultEval (repl.js:240:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:441:10)
    at emitOne (events.js:121:20)
    at REPLServer.emit (events.js:211:7)
>

虽然说,可以使用call方法来指定this

> let o = {};
undefined
> let g = range.call(o, 1, 3);
undefined
> g.next();
{ value: 1, done: false }
> o.i;
1
> g.next();
{ value: 2, done: false }
> o.i;
2
> g.i;
undefined
>

然而,this绑定的对象跟生成器没有关系,若你真的有相似的需求,何不直接让它明确一点呢?

> function* range(obj, start, end) {
...     for(obj.i = start; obj.i < end; obj.i++) {
.....         yield obj.i;
.....     }
... }
undefined
> let o = {};
undefined
> let g = range(o, 1, 3);
undefined
> g.next();
{ value: 1, done: false }
> o.i;
1
> g.next();
{ value: 2, done: false }
> o.i;
2
>




展开阅读全文