在〈簡介產生器函式〉中看到的產生器函式,都只使用了 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
特性會是 true
,value
特性會是 undefined
,如果 return
方法有指定引數,那麼傳回的物件 done
特性會是 true
,value
特性會是指定的引數。
類似地,產生器有個 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)
>
在 range
的 prototype
上,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
>