摘要:最后,調用這個函數,得到一個遍歷器對象并賦值給變量。值得注意的是如果函數內部沒有部署代碼塊,那么遍歷器對象的方法拋出的錯誤,將被外部代碼塊捕獲。
本文已同步至我的個人主頁。歡迎訪問查看更多內容!如有錯誤或遺漏,歡迎隨時指正探討!謝謝大家的關注與支持!
一、什么是Generator函數Generator函數是ES6標準中提出的一種異步編程的解決方案。這種函數與普通函數最大的區別在于它可以暫停執行,又可以從暫停的位置恢復繼續執行。
從語法上看,Generator函數就是一個狀態機,封裝了許多內部狀態。
從實質上看,Generator函數就是一個遍歷器對象生成器。(關于遍歷器對象,可以參考阮一峰老師的這篇文章)Generator函數返回一個遍歷器對象,遍歷這個對象,就可以依次得到函數內部的每一個狀態。
二、基本語法 1、定義Generator函數定義一個Generator函數和定義一個普通函數的區別在于:
function關鍵字和函數名之間有一個 *(星號)。
函數內部使用yield來定義每一個函數內部的狀態。
如果函數內部有return語句,那么他就是函數內部的最后一個狀態。
來看一個簡單的例子:
// 定義 function* sayHello() { yield "hello"; yield "world"; return "ending"; } // 調用 // 注意,hw獲取到的值是一個遍歷器對象 let g = sayHello();
上面的例子,定義了一個名為sayHello的Generator函數,它內部有兩個yield表達式和一個return表達式。所以,該函數內部有三個狀態:hello,world 和 return語句(結束執行)。最后,調用這個函數,得到一個遍歷器對象并賦值給變量g。
Generator函數的調用方法與普通函數完全一樣,函數名()。不同的是:
函數調用后,內部代碼(從第一行開始)都不會立即執行。
函數調用后會有一個返回值,這個值是一個指向內部狀態的指針對象,實質就是一個包含函數內部狀態的遍歷器對象。
Generator函數調用后不會立即執行,那么,我們如何讓它開始執行內部的代碼呢?又如何獲取它內部的每一個狀態呢?此時,我們必須調用返回的生成器對象的.next()方法,才能開始代碼的執行,并且使得指針移向下一個狀態。
以上面的例子為例:
g.next(); // { value: "hello", done: false } g.next(); // { value: "world", done: false } g.next(); // { value: "ending", done: true } g.next(); // { value: undefined, done: true }
上面的代碼中,一共調用了四次g這個遍歷器對象的.next()方法。第一次調用,sayHello這個Generator函數開始執行,直到遇到第一個yield表達式就會暫停執行。.next()方法會返回一個對象,它的value屬性就是當前yield表達式的值hello,done屬性的值false,表示遍歷還沒有結束。
第二次再調用.next(),就會執行到第二個yield表達式處,并暫停執行,返回對應的對象。
第三次調用.next(),函數執行到最后的return語句,此時標志著遍歷器對象g遍歷結束,所以返回的對象中value屬性值就是return后面所跟的值ending,done屬性值為true,表示遍歷已經結束。
第四次以及后面在調用.next()方法,返回的都會是{value: undefined, done: true }。
2、yield表達式由Generator函數返回的遍歷器對象,只有調用.next()方法才會遍歷到下一個內部狀態,所以這其實是提供了一種可以暫停執行的函數,yield表達式就是暫停標志。
遍歷器對象的.next()方法的運行邏輯如下。
遇到yield表達式,就暫停執行后面的操作,并將緊跟在yield后面的那個表達式的值,作為返回的對象的value屬性值。
下一次調用.next()方法時,再繼續往下執行,直到遇到下一個yield表達式。
如果沒有再遇到新的yield表達式,就一直運行到函數結束,直到return語句為止,并將return語句后面的表達式的值,作為返回的對象的value屬性值。
如果該函數沒有return語句,則返回的對象的value屬性值為undefined。
值得注意的是:
yield關鍵字只能出現在Generator函數中,出現在別的函數中會報錯。
// 出現在普通函數中,報錯 (function () { yield "hello"; })() // forEach不是Generator函數,報錯 [1, 2, 3, 4, 5].forEach(val => { yield val });
yield關鍵字后面跟的表達式,是惰性求值的。 只有當調用.next()方法、內部狀態暫停到當前yield時,才會計算其后面跟的表達式的值。這等于為JavaScript提供了手動的“惰性求值”的語法功能。
function* step() { yield "step1"; // 下面的yield后面的表達式不會立即求值, // 只有暫停到這一行時,才會計算表達式的值。 yield "step" + 2; yield "setp3"; return "end"; }
yield表達式本身是沒有返回值的,或者說它的返回值為undefined。使用.next()傳參可以為其設置返回值。(后面會講到)
function* gen() { for (let i = 0; i < 5; i++) { let res = yield; // yield表達式本身沒有返回值 console.log(res); // undefined } } let g = gen(); g.next(); // {value: 0, done: false} g.next(); // {value: 1, done: false} g.next(); // {value: 2, done: false}
yield與return的異同:
相同點:
兩者都能返回跟在其后面的表達式的值。
不同點:
yield表達式只是暫停函數向后執行,return是直接結束函數執行。
yield表達式可以出現多次,后面還可以有代碼。return只能出現一次,后面的代碼不會執行,在一些情況下還會報錯。
正常函數只能返回一個值,因為只能執行一次return。Generator函數可以返回一系列的值,因為可以有任意多個yield。
3、.next()方法傳參前面我們說到過,yield表達式自身沒有返回值,或者說返回值永遠是undefined。但是,我們可以通過給.next()方法傳入一個參數,來設置上一個(是上一個)yield表達式返回值。
來看一個例子:
function* conoleNum() { console.log("Started"); console.log(`data: ${yield}`); console.log(`data: ${yield}`); return "Ending"; } let g = conoleNum(); g.next(); // 控制臺輸出:"Started" g.next("a"); // 控制臺輸出:"data: a" // 不傳入參數"a",就會輸出"data: undefined" g.next("b"); // 控制臺輸出:"data: b" // 不傳入參數"a",就會輸出"data: undefined"
上面的例子,需要強調一個不易理解的地方。
第一次調用.next(),此時函數暫停在代碼第三行的yield表達式處。記得嗎?yield會暫停函數執行,此時打印它的console.log(),也就是代碼第三行的console,由于暫停并沒有被執行,所以不會打印出結果,只輸出了代碼第二行的"Started"。
當第二次調用.next()方法時,傳入參數"a",函數暫停在代碼第四行的yield語句處。此時參數"a"會被當做上一個yield表達式的返回值,也就是代碼第三行的yiled表達式的返回值,所以此時控制臺輸出"data: a"。而代碼第四行的console.log()由于暫停,沒有被輸出。
第三次調用,同理。所以輸出"data: b"。
4、Generator.prototype.throw()Generator函數返回的遍歷器對象,都有一個.throw()方法,可以在函數體外拋出錯誤,然后在Generator函數體內捕獲。
function* gen() { try { yield; } catch (e) { console.log("內部捕獲", e); } }; var g = gen(); // 下面執行一次.next() // 是為了讓gen函數體執行進入try語句中的yield處 // 這樣拋出錯誤,gen函數內部的catch語句才能捕獲錯誤 g.next(); try { g.throw("a"); g.throw("b"); } catch (e) { console.log("外部捕獲", e); }
上面例子中,遍歷器對象g在gen函數體外連續拋出兩個錯誤。第一個錯誤被gen函數體內的catch語句捕獲。g第二次拋出錯誤,由于gen函數內部的catch語句已經執行過了,不會再捕捉到這個錯誤了,所以這個錯誤就會被拋出gen函數體,被函數體外的catch語句捕獲。
值得注意的是:
如果Generator函數內部沒有部署try...catch代碼塊,那么遍歷器對象的throw方法拋出的錯誤,將被外部try...catch代碼塊捕獲。
如果Generator函數內部和外部都沒有部署try...catch代碼塊,那么程序將報錯,直接中斷執行。
遍歷器對象的throw方法被捕獲以后,會附帶執行一次.next()方法,代碼執行會暫停到下一條yield表達式處。看下面這個例子:
function* gen(){ try { yield console.log("a"); } catch (e) { console.log(e); // "Error" } yield console.log("b"); yield console.log("c"); } var g = gen(); g.next(); // 控制臺輸出:"a" g.throw("Error"); // 控制臺輸出:"b" // throw的錯誤被內部catch語句捕獲, // 會自動在執行一次g.next() g.next(); // 控制臺輸出:"c"5、Generator.prototype.return()
Generator函數返回的遍歷器對象,還有一個.return()方法,可以返回給定的值,并且直接結束對遍歷器對象的遍歷。
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next(); // { value: 1, done: false } // 提前結束對g的遍歷。盡管yield還沒有執行完 // 此時done屬性值為true,說明遍歷結束 g.return("foo"); // { value: "foo", done: true } g.next(); // { value: undefined, done: true }
如果.return()方法調用時,不提供參數,則返回值的value屬性為undefined。
6、yield* 表達式yield* 用來在一個Generator函數里面執行另一個Generator函數。
如果在一個Generator函數內部,直接調用另一個Generator函數,默認情況下是沒有效果的。
function* gen1() { yield "a"; yield "b"; } function* gen2() { yield "x"; // 直接調用gen1() gen1(); yield "y"; } // 遍歷器對象可以使用for...of遍歷所有狀態 for (let v of gen2()){ 只輸出了gen1的狀態 console.log(v); // "x" "y" }
上面的例子中,gen1和gen2都是Generator函數,在gen2里面直接調用gen1,是不會有效果的。
這個就需要用到 yield* 表達式。
function* gen1() { yield "a"; yield "b"; } function* gen2() { yield "x"; // 用 yield* 調用gen1() yield* gen1(); yield "y"; } for (let v of gen2()){ 輸出了gen1、gen2的狀態 console.log(v); // "x" "a" "b" "y" }小節
本文主要講解Generator函數的基本語法和一些細節,Generator函數的定義、yield表達式、.next()方法及傳參、.throw()方法、.return()方法以及 yield* 表達式。
文章開頭講到,Generator函數時ES6提出的異步編程的一種解決方案。在實際應用中,一般在yield關鍵字后面會跟隨一個異步操作,當異步操作成功返回后調用.next()方法,將異步流程交給下一個yield表達式。具體關于Generator函數的異步應用,大家可以參考阮一峰老師的這篇文章,或參考其他網上資料,繼續深入學習。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103133.html
摘要:調用函數后和普通函數不同的是,該函數并不立即執行,也不返回函數執行結果,而是返回一個指向內部狀態的對象,也可以看作是一個遍歷器對象。第一個只是用來啟動函數內部的遍歷器,傳參也沒有多大意義。 之前斷斷續續接觸到了一些ES6的知識,異步編程方面聽得比較多的就是Promise,直到最近比較系統地學習了ES6的新特性才發現Generator這個神奇的存在,它可以實現一些前所未有的事情,讓我頓時...
摘要:同時,迭代器有一個方法來向函數中暫停處拋出一個錯誤,該錯誤依然可以通過函數內部的模塊進行捕獲處理。 本文翻譯自:Diving Deeper With ES6 Generators 由于個人能力有限,翻譯中難免有紕漏和錯誤,望不吝指正issue ES6 Generators:完整系列 The Basics Of ES6 Generators Diving Deeper With E...
摘要:關于協程和中的什么是協程進程和線程眾所周知,進程和線程都是一個時間段的描述,是工作時間段的描述,不過是顆粒大小不同,進程是資源分配的最小單位,線程是調度的最小單位。子程序就是協程的一種特例。 關于協程和 ES6 中的 Generator 什么是協程? 進程和線程 眾所周知,進程和線程都是一個時間段的描述,是CPU工作時間段的描述,不過是顆粒大小不同,進程是 CPU 資源分配的最小單位,...
摘要:示例運行函數彈出彈出函數接收參數,返回值。其中,返回一個對象,是的返回值,代表函數是否執行完成。 ES6特性介紹(下) ES6新的標準,新的語法特征:1、變量/賦值2、函數3、數組/json4、字符串5、面向對象6、Promise7、generator8、ES7:async/await 《【Web全棧課程二】ES6特性介紹(上)》見:https://segmentfault.com/a...
摘要:換句話說,我們很好的對代碼的功能關注點進行了分離通過將使用消費值得地方函數中的邏輯和通過異步流程來獲取值迭代器的方法進行了有效的分離。但是現在我們通過來管理代碼的異步流程部分,我們解決了回調函數所帶來的反轉控制等問題。 本文翻譯自 Going Async With ES6 Generators 由于個人能力知識有限,翻譯過程中難免有紕漏和錯誤,還望指正Issue ES6 Gener...
摘要:前言本文就是簡單介紹下語法編譯后的代碼。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎,對作者也是一種鼓勵。 前言 本文就是簡單介紹下 Generator 語法編譯后的代碼。 Generator function* helloWorldGenerator() { yield hello; yield world; return ending...
閱讀 1827·2021-11-11 16:55
閱讀 1452·2019-08-30 15:54
閱讀 769·2019-08-29 15:34
閱讀 2253·2019-08-29 13:11
閱讀 2908·2019-08-26 13:28
閱讀 1878·2019-08-26 10:49
閱讀 992·2019-08-26 10:40
閱讀 2553·2019-08-23 18:21