ES6中Generator 函式
什麼是generator
Generator 函式有多種理解角度。語法上,首先可以把它理解成,Generator 函式是一個狀態機,封裝了多個內部狀態。
執行 Generator 函式會返回一個遍歷器物件,也就是說,Generator 函式除了狀態機,還是一個遍歷器物件生成函式。返回的遍歷器物件,可以依次遍歷 Generator 函式內部的每一個狀態。
形式上,Generator 函式是一個普通函式,但是有兩個特徵。一是,function
關鍵字與函式名之間有一個星號;二是,函式體內部使用yield
表示式,定義不同的內部狀態
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();
yield
和return
語句後的就是一個狀態
Generator 函式的呼叫方法與普通函式一樣,也是在函式名後面加上一對圓括號。不同的是,呼叫 Generator
函式後,該函式並不執行,返回的也不是函式執行結果,而是一個指向內部狀態的指標物件(iterator物件
)
呼叫該物件的next()
方法時,內部指標就從函式頭部或上一次停下來的地方開始執行,直到遇到下一個yield
表示式(或return語句)
邏輯如下
-
遇到
yield
表示式,就暫停執行後面的操作,並將緊跟在yield
後面的那個表示式的值,作為返回的物件的value屬性值。該表示式為惰性求值
-
下一次呼叫
next
方法時,再繼續往下執行,直到遇到下一個yield
-
如果沒有再遇到新的
yield
表示式,就一直執行到函式結束,直到return
語句為止,並將return
語句後面的表示式的值,作為返回的物件的value屬性值。 -
如果該函式沒有return語句,則返回的物件的
value
屬性值為undefine
d。
- 換言之,Generator 函式是分段執行的,yield表示式是暫停執行的標記,而next方法可以恢復執行。
與iterator介面的關係
Generator 函式就是遍歷器生成函式,因此可以把 Generator 賦值給物件的Symbol.iterator屬性,從而使得該物件具有 Iterator 介面
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
Generator 函式執行後,返回一個遍歷器物件。該物件本身也具有Symbol.iterator屬性,執行後返回自身。
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
next方法的引數
yield表示式本身沒有返回值,或者說總是返回undefined。next方法可以帶一個引數,該引數就會被當作上一個yield表示式的返回值。
function* f() {
var te = yield 1 // yield的預設返回值為undefined
console.log(te)
yield 2
yield 3
}
var g = f();
console.log(g.next()) // { value: 0, done: false }
console.log(g.next(2)) // 2,{ value: 1, done: false }
例如這裡的te作為第一次yield的返回值是undefined
,但是第二次呼叫的時候傳入了引數2,就會把第二次呼叫next
傳入的引數作為第一次的結果返回,所以這裡te的值為2
for...of...迴圈
for...of迴圈可以自動遍歷 Generator 函式執行時生成的Iterator物件,且此時不再需要呼叫next方法
- 一旦next方法的返回物件的
done
屬性為true
,for...of迴圈就會中止,且不包含該返回物件
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
為物件加上遍歷介面
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj); // 獲取所有鍵
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
或者直接加到原型鏈上面
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries; // 當一個物件使用for...of 迴圈時,會自動尋找物件的jane[Symbol.iterator]並且執行
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
- 擴充套件運算子(...)、解構賦值和Array.from方法內部呼叫的,都是遍歷器介面
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 擴充套件運算子
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解構賦值
let [x, y] = numbers();
x // 1
y // 2
// for...of 迴圈
for (let n of numbers()) {
console.log(n)
}
// 1
// 2
throw()方法
-
遍歷器內部的try...catch...配合只能捕獲遍歷器已經至少執行過
一次next
的遍歷器內部的錯誤,並且只會執行一次
,在捕獲錯誤的同時也會執行一次next
-
遍歷器物件的throw方法和全域性的throw命令
互不干擾
-
如果 Generator 函式內部沒有部署try...catch程式碼塊,那麼throw方法丟擲的錯誤,將被
外部
try...catch程式碼塊捕獲。 -
如果 Generator 函式內部和外部,
都沒有
部署try...catch程式碼塊,那麼程式將報錯,直接中斷執行
。 -
Generator
函式體外
丟擲的錯誤,可以在函式體內捕獲;反過來,Generator函式體內
丟擲的錯誤,也可以被函式體外的catch捕獲。 -
一旦 Generator 執行過程中丟擲錯誤,且沒有被內部捕獲,就不會再執行下去了。如果此後還呼叫next方法,將返回一個value屬性等於undefined、done屬性等於true的物件,即 JavaScript 引擎認為這個 Generator 已經執行結束了。
return()方法
-
使用return方法終結遍歷 Generator 函式,返回值的
value
屬性就是return()方法的引數foo
,返回值的done屬性為true
-
如果 Generator 函式內部有try...finally程式碼塊,且正在執行try程式碼塊,那麼return()方法會導致立刻進入finally程式碼塊,執行完以後,整個函式才會結束。
yield* 表示式
- 用來在一個 Generator 函式裡面執行另一個 Generator 函式。如果執行另一個 Generator 函式的時候不加
*
號就會返回該yield
命令後的遍歷器物件 - yield*後面的 Generator 函式(沒有return語句時),等同於在 Generator 函式內部,部署一個
for...of
迴圈。 - 如果被代理的 Generator 函式有return語句,那麼就可以向代理它的 Generator 函式返回資料。
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一個遍歷器物件
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
作為物件的屬性
let obj = {
* myGeneratorMethod() {
···
}
};