PAT甲級1151LCA in a Binary Tree
1、什麼是Generator函式
在javascript中,一個函式一旦開始執行,就會執行到最後或者遇到return時結束,執行期間不會有其他程式碼能夠打斷它,也不能從外部再傳入值到函式體內
而Generator函式(生成器)的出現使得打破函式的完整執行成為了可能,其語法行為與傳統函式完全不同,Generator函式是es6提供的一種非同步程式設計解決方案,形式上也是一個普通函式,但是有幾個顯著的特徵:
---function關鍵字與函式名之間有一個*
---函式體內使用yield表示式,定義不同的內部狀態(可以有多個yield)
---直接呼叫generator函式並不會執行,也不會返回執行結果,而是返回一個遍歷器物件
---依次呼叫遍歷器物件的next方法,遍歷generator內部的每一個狀態
1 { 2 // 傳統函式 3 function foo() { 4 return 'hello world' 5 } 6 7 foo() // 'hello world',一旦呼叫立即執行 8 9 10 // Generator函式 11 function* generator() { 12 yield 'status one' // yield 表示式是暫停執行的標記 13 return 'hello world' 14 }15 16 let iterator = generator() // 呼叫 Generator函式,函式並沒有執行,返回的是一個Iterator物件 17 iterator.next() // {value: "status one", done: false},value 表示返回值,done 表示遍歷還沒有結束 18 iterator.next() // {value: "hello world", done: true},value 表示返回值,done 表示遍歷結束 19 }
上面程式碼中可以看到傳統函式和generator函式的執行是完全不同的,
傳統函式呼叫後立即執行並輸出了返回值;
generator函式則沒有執行而是返回一個iterator物件,並通過呼叫iterator物件的next方法來遍歷,函式體內的執行看起來更像是“被人踢一腳才動一下”的感覺
1 { 2 function* gen() { 3 yield 'hello' 4 yield 'world' 5 return 'ending' 6 } 7 8 let it = gen() 9 10 it.next() // {value: "hello", done: false} 11 it.next() // {value: "world", done: false} 12 it.next() // {value: "ending", done: true} 13 it.next() // {value: undefined, done: true} 14 }
上面程式碼中定義了一個generator函式,其中包括2個yield表示式和一個return語句(即產生了三個狀態)
每次呼叫iterator物件的next方法時,內部的指標就會從函式的頭部或者上一次停下來的地方開始執行,知道遇到下一個yield表示式或者return語句暫停。換句話說,generator函式是分段執行的,yield表示式是暫停執行的標記,而next方法可以恢復執行
執行過程如下:
第一次呼叫next方法時,內部指標從函式頭部開始執行,遇到第一個yield表示式暫停,並返回當前狀態的值“hello”
第二次呼叫next方法時,內部指標從上一個(即第一個)yield表示式開始,遇到第二個yield表示式暫停,返回當前狀態的值“world”
第三次呼叫next方法時,內部指標從第二個yield表示式開始,遇到return語句暫停,返回當前狀態的值“ending”,同時所有狀態遍歷完畢,done屬性的值變為true
第四次呼叫next方法時,由於函式已經遍歷執行完畢,不再有其他狀態,因此返回{value:undefined,done:true}.如果繼續呼叫next方法,返回的也是都是這個值
2、yied表示式
(1)yield表示式只能用在generator函式裡面,用在其他地方都會報錯
(2)yield表示式如果用在另一個表示式中,必須放在圓括號裡面
1 { 2 function* demo() { 3 console.log('Hello' + yield); // SyntaxError 4 console.log('Hello' + yield 123); // SyntaxError 5 6 console.log('Hello' + (yield)); // OK 7 console.log('Hello' + (yield 123)); // OK 8 } 9 }
(3)yield表示式用作引數或放在賦值表示式的右邊,可以不加括號
1 { 2 function* demo() { 3 foo(yield 'a', yield 'b'); // OK 4 let input = yield; // OK 5 } 6 }
(4)yield表示式和return語句的區別
相似:都能返回緊跟在語句後面的那個表示式的值
區別:---每次遇到yield,函式就暫停一下,下一次再從該位置繼續向後執行;而return語句不具備記憶位置的功能
---一個函式只能執行一次return語句,而在generator函式中可以有任意多個yield
3、yield*表示式
如果在generator函式裡面呼叫另一個generator函式,預設情況下是沒有效果的
1 { 2 function* foo() { 3 yield 'aaa' 4 yield 'bbb' 5 } 6 7 function* bar() { 8 foo() 9 yield 'ccc' 10 yield 'ddd' 11 } 12 13 let iterator = bar() 14 15 for(let value of iterator) { 16 console.log(value) 17 } 18 19 // ccc 20 // ddd 21 22 }
上例中,使用for...of遍歷函式bar生成的遍歷器物件時,值返回了bar自身的兩個狀態值。此時,如果想要正確的在bar裡呼叫foo,就需要用到yield*表示式
yield*表示式用來在一個generator函式裡面執行另一個generator函式
1 { 2 function* foo() { 3 yield 'aaa' 4 yield 'bbb' 5 } 6 7 function* bar() { 8 yield* foo() // 在bar函式中 **執行** foo函式 9 yield 'ccc' 10 yield 'ddd' 11 } 12 13 let iterator = bar() 14 15 for(let value of iterator) { 16 console.log(value) 17 } 18 19 // aaa 20 // bbb 21 // ccc 22 // ddd 23 }
4、next()方法的引數
yield表示式本身沒有返回值,或者說總是返回undefined。next方法可以帶一個引數,該引數就會被當作上一個yield表示式的返回值
1 [rv] = yield [expression] 2 3 expression:定義通過遍歷器從生成器函式返回的值,如果省略,則返回 undefined 4 rv:接收從下一個 next() 方法傳遞來的引數
1 { 2 function* gen() { 3 let result = yield 3 + 5 + 6 4 console.log(result) 5 yield result 6 } 7 8 let it = gen() 9 console.log(it.next()) // {value: 14, done: false} 10 console.log(it.next()) // undefined {value: undefined, done: false} 11 }
第一次呼叫遍歷物件的next方法,函式從頭部開始執行,遇到第一個yield暫停,在這個過程中其實是分了三步:
(1)申明一個變數result,並將申明提前,預設值為undefined
(2)由於generator函式是“惰性求值”,執行到第一個yield時才會計算求和,並加計算結果返回給遍歷器物件 {value:14,done:false},函式暫停執行