摘要:返回的遍歷器對(duì)象,可以依次遍歷函數(shù)內(nèi)部的每一個(gè)狀態(tài)。由于函數(shù)就是遍歷器生成函數(shù),因此可以把賦值給對(duì)象的屬性,從而使得該對(duì)象具有接口。函數(shù)從暫停狀態(tài)恢復(fù)運(yùn)行,它的上下文狀態(tài)時(shí)不變的。從語(yǔ)義上講,第一個(gè)方法用來(lái)啟動(dòng)遍歷器對(duì)象,所以不用帶有參數(shù)。
基本概念
Generator函數(shù)是ES6提供的一種異步編程解決方案,語(yǔ)法行為與傳統(tǒng)函數(shù)完全不同。Generator函數(shù)有多種理解角度。語(yǔ)法上,首先可以把它理解成,Generator函數(shù)是一個(gè)狀態(tài)機(jī),封裝了多個(gè)內(nèi)部狀態(tài)。
執(zhí)行Generator函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象,也就是說(shuō),Generator函數(shù)除了狀態(tài)機(jī),還是一個(gè)遍歷器對(duì)象生成函數(shù)。返回的遍歷器對(duì)象,可以依次遍歷Generator函數(shù)內(nèi)部的每一個(gè)狀態(tài)。
形式上,Generator函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。一是,function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);二是,函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)(yield在英語(yǔ)里的意思就是產(chǎn)出)。
1.語(yǔ)法。
[rv] = yield [expression];
expression:定義通過(guò)迭代器協(xié)議從生成器函數(shù)返回的值。如果省略,則返回undefined。
rv:返回傳遞給生成器的next()方法的可選值,以恢復(fù)其執(zhí)行。
2.描述
yield關(guān)鍵字使生成器函數(shù)執(zhí)行暫停,yield關(guān)鍵字后面的表達(dá)式的值返回給生成器的調(diào)用者。它可以被認(rèn)為是一個(gè)基于生成器的版本return關(guān)鍵字。
yield關(guān)鍵字實(shí)際返回一個(gè)IteratorResult對(duì)象,它有兩個(gè)屬性,value和done。value屬性是對(duì)yield表達(dá)式求值的結(jié)果,而done是false,表示生成器函數(shù)尚未完全完成。
*yield,導(dǎo)致生成器再次暫停并返回生成器的新值。下一次調(diào)用next()時(shí),在yield之后緊接著的語(yǔ)句繼續(xù)執(zhí)行。 *throw用于從生成器中拋出異常。這讓生成器完全停止執(zhí)行,并在調(diào)用者中繼續(xù)執(zhí)行,正如通常情況下拋出異常一樣。 *到達(dá)生成器函數(shù)結(jié)尾;在這種情況下,生成器的執(zhí)行結(jié)束,并且IteratorResult給調(diào)用者返回undefined并且done為true。 *到達(dá)return語(yǔ)句。這種情況下,生成器的執(zhí)行結(jié)束,并將IterratorResult返回給調(diào)用者,其值是由return語(yǔ)句指定的,并且done為true。
如果將可選值傳遞給生成器的next()方法,則該值將稱為生成器當(dāng)前yield操作返回的值。
在生成器的代碼路徑中yield運(yùn)算符,以及通過(guò)將其傳遞給Generator.prototype.next()指定新的起始值的能力之間,生成器提供了強(qiáng)大的控制力。
3.實(shí)例.
function*countAppleSales(){ var arr = [1,2,3]; for (var i=0;iGenerator函數(shù)可以不用yield表達(dá)式,這時(shí)就變成了一個(gè)單純的暫緩執(zhí)行函數(shù)。另外需要注意,yield表達(dá)式只能用在Generator函數(shù)里面,用在其它地方都會(huì)報(bào)錯(cuò)。
function*f(){ console.log("執(zhí)行了!"); } var generator = f(); generator.next(); //上面代碼中,函數(shù)f如果是普通函數(shù),在為變量generator賦值時(shí)就會(huì)執(zhí)行。但是,函數(shù)f是一個(gè)Generator函數(shù),就變成只有調(diào)用next方法時(shí)才會(huì)執(zhí)行。下面是另一個(gè)例子:
var arr=[1,[[2,3],4],[5,6]]; var flat = function*(a){ a.forEach(function(item){ if(typeof item!=="niumber"){ yield*flat(item); }else{ yield item; } }); } for (var f of flat(arr)){ console.log(f); }上面代碼也會(huì)產(chǎn)生語(yǔ)句錯(cuò)誤,因?yàn)閒orEach方法的參數(shù)是一個(gè)普通函數(shù),但是在里面使用了yield表達(dá)式。一種修改方法是改用for循環(huán)。
var arr=[1,[[2,3],4],[5,6]]; var flat = function*(a){ var length = a.length; for(var i=0;i另外,yield表達(dá)式如果用在另一個(gè)表達(dá)式中,必須放在圓括號(hào)里面。
function*demo(){ console.log("Hellow"+yield);//SyntaxError console.log("Hellow"+yield 123);//SyntaxError console.log("Hellow"+(yield));//ok console.log("Hellow"+(yield 123));//ok }yield表達(dá)式用作函數(shù)參數(shù)或放在賦值表達(dá)式的右邊,可以不加括號(hào)。
function*demo(){ foo(yield "a",yield "b");//ok let input = yield;//ok }與Iterator接口的關(guān)系任意一個(gè)對(duì)象的symbol.iterator方法,等于該對(duì)象的遍歷器生成函數(shù),調(diào)用該函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象。由于Generator函數(shù)就是遍歷器生成函數(shù),因此可以把Generator賦值給對(duì)象的symbol.iterator屬性,從而使得該對(duì)象具有Iterator接口。
var myIterator = {}; myIterator[Symbol.iterator] = function*(){ yield 1; yield 2; yield 3; } [...myIterator]//[1,2,3]上面代碼中,Generator函數(shù)賦值給Symbol.iterator屬性,從而使得myIterable對(duì)象具有了Iterator接口,可以被...運(yùn)算遍歷了。
Generator函數(shù)執(zhí)行后,返回一個(gè)遍歷器對(duì)對(duì)象。該對(duì)象本身也具有Symbol.iterator屬性,執(zhí)行后返回自身。function* gen(){ // some code } var g = gen(); g[Symbol.iterator]() === g // true上面代碼中,gen是一個(gè)Generator函數(shù),調(diào)用它會(huì)生成一個(gè)遍歷器對(duì)象g。它的Symbol.iterator屬性,也是一個(gè)遍歷器對(duì)象生成函數(shù),執(zhí)行后返回它自己。
next方法的參數(shù)yield表達(dá)式本身沒(méi)有返回值,或者說(shuō)總是返回undefined。next方法可以帶一個(gè)參數(shù),該參數(shù)就會(huì)被當(dāng)作上一個(gè)yield表達(dá)式的返回值。
function*f(){ for(var i=0;true;i++){ var reset = yield i; if(reset){i=-1} } } var g = f(); g.next()//{value:0,done:false} g.next()//{value:1,done:false} g.next(true)//{value:0,done:false}上面代碼先定義了一個(gè)可以無(wú)限運(yùn)動(dòng)的Generator函數(shù)f,如果next方法沒(méi)有參數(shù),每次運(yùn)行到y(tǒng)ield表達(dá)式,變量reset的值總是undefined。當(dāng)next方法帶一個(gè)參數(shù)true時(shí),變量reset就被重置為這個(gè)參數(shù)(即true),因此i會(huì)等于-1,下一輪循環(huán)就會(huì)從-1開(kāi)始遞增。
這個(gè)功能有很重要的語(yǔ)法意義。Generator函數(shù)從暫停狀態(tài)恢復(fù)運(yùn)行,它的上下文狀態(tài)時(shí)不變的。通過(guò)next方法的參數(shù),就有辦法在Generator函數(shù)開(kāi)始運(yùn)行之后,繼續(xù)向函數(shù)內(nèi)部注入值。也就是說(shuō),可以在Generator函數(shù)運(yùn)行的不同階段,從外部向內(nèi)部注入不同的值,從而調(diào)整函數(shù)行為。
再看一個(gè)例子。function*foo(x){ var y = 2*(yield(x+1)); var z = yield(y/3); return (x+y+z); } // var a = foo(5); // console.log(a.next());//{value:6,done:false}; // console.log(a.next());//{value:NaN,done:false}因?yàn)榇藭r(shí)y是undefined // console.log(a.next());//{value:NaN,done:true} var b = foo(5); console.log(b.next());//{value:6,done:false}; console.log(b.next(12));//{value:8,done:false} 此時(shí)y=2*第一個(gè)yield的返回值(12)。 console.log(b.next(13));//z=13,y=24,x=5.{ value:42, done:true }上面代碼中,第二次運(yùn)行next方法的時(shí)候不帶參數(shù),導(dǎo)致y的值等于2*undefined(即NaN),除以3以后還是NaN,因此返回對(duì)象的value屬性也等于NaN。第三次運(yùn)行next方法的時(shí)候不帶參數(shù),所以z等于undefined,返回對(duì)象的value屬性等于5+NaN+undefined,即NaN。
如果向next方法提供參數(shù),返回結(jié)果就完全不一樣了。上面代碼第一次調(diào)用b的next方法時(shí),返回x+1=6;第二次調(diào)用next方法,將上一次yield表達(dá)式的值設(shè)為12,因此y等于24,返回y/3的值8;第三次調(diào)用next方法,將上一次yield表達(dá)式的值設(shè)為13,因此z等于13,這時(shí)x等于5,y等于24,所以return語(yǔ)句的值等于42.
注意:由于next方法的參數(shù)表示上一個(gè)yield表達(dá)式的返回值,所以在第一次使用next方法時(shí),傳遞參數(shù)是無(wú)效的。V8引擎直接忽略第一次使用next方法時(shí)的參數(shù),只有從第二次使用next方法開(kāi)始,參數(shù)才是有效的。從語(yǔ)義上講,第一個(gè)next方法用來(lái)啟動(dòng)遍歷器對(duì)象,所以不用帶有參數(shù)。
再看一個(gè)通過(guò)next方法的參數(shù),向Generator函數(shù)內(nèi)部輸入值的例子。function*dataConsumer(){ console.log("Started"); console.log(`1. ${yield}`); console.log(`2. ${yield}`); return "result"; } let genObj = dataConsumer(); function* dataConsumer(){ console.log("Started"); console.log(`1. ${yield}`); console.log(`2. ${yield}`); return "result"; } alet genObj = dataConsumer(); genObj.next(); //Started genObj.next("a"); //1.a genObj.next("b"); //2.b上面代碼是一個(gè)很直觀的例子,每次通過(guò)next方法向Generator函數(shù)輸入值,然后打印出來(lái)。
如果想要第一次調(diào)用next方法時(shí),就能夠輸入值,可以在Generator函數(shù)外面再包一層。function wrapper(generatorFunction){ return function(){ let generatorObject = generatorFunction(); generatorObject.next(); return generatorObject; }; } const wrapped = wrapper(function*(){ console.log(`First input: ${yield}`); return "DONE"; }); wrapped().next("hello!"); // console.log(wrapped().next("hello!"))上面代碼中,Generator函數(shù)如果不用wrapper先包一層,是無(wú)法第一次調(diào)用next方法,就輸入?yún)?shù)的。
for...of循環(huán)for...of循環(huán)可以自動(dòng)遍歷Generator函數(shù)時(shí)生成Iterator對(duì)象,且此時(shí)不再需要調(diào)用next方法。
function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5上面代碼使用for...of循環(huán),依次顯示5個(gè)yield表達(dá)式的值。這里需要注意,一旦next方法的返回對(duì)象的done屬性為true,for...of循環(huán)就會(huì)中止,且不包含該返回對(duì)象,所以上面代碼的return語(yǔ)句返回的6,不包括在for...of循環(huán)中。
下面是一個(gè)利用Generator函數(shù)for...of循環(huán),實(shí)現(xiàn)斐波那契數(shù)列的例子。function*fibonacci(){ let [prev,curr] = [0,1]; for(;;){ yield curr; [prev,curr] = [curr,prev+curr]; } } for(let n of fibonacci){ if(n>100)break; console.log(n); }利用for...of循環(huán),可以寫(xiě)出遍歷任意對(duì)象的方法。原生JavaScript對(duì)象沒(méi)有遍歷接口,無(wú)法使用for...of循環(huán),通過(guò)Gneerator函數(shù)為它加上這個(gè)接口,就可以用了。
//Reflect.ownKeys()方法返回target對(duì)象自己的屬性鍵的數(shù)組。 //Reflect.ownKeys({z:3,y:2,x:1});//["z","y","x"]; //Reflect.ownKeys([]);//["length"]; //Reflect.ownKeys([1,2,3,4]);//["1","2","3","4","length"] 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上面代碼中,對(duì)象jane原生不具備 Iterator 接口,無(wú)法用for...of遍歷。這時(shí),我們通過(guò) Generator 函數(shù)objectEntries為它加上遍歷器接口,就可以用for...of遍歷了。加上遍歷器接口的另一種寫(xiě)法是,將 Generator 函數(shù)加到對(duì)象的Symbol.iterator屬性上面。
//Object.keys 返回一個(gè)所有元素為字符串的數(shù)組,其元素來(lái)自于從給定的object上面可直接枚舉的屬性。這些屬性的順序與手動(dòng)遍歷該對(duì)象屬性時(shí)的一致。 // simple array var arr = ["a", "b", "c"]; console.log(Object.keys(arr)); // console: ["0", "1", "2"] // array like object var obj = { 0: "a", 1: "b", 2: "c" }; console.log(Object.keys(obj)); // console: ["0", "1", "2"] // array like object with random key ordering var anObj = { 100: "a", 2: "b", 7: "c" }; console.log(Object.keys(anObj)); // console: ["2", "7", "100"] // getFoo is a property which isn"t enumerable var myObj = Object.create({}, { getFoo: { value: function () { return this.foo; } } }); myObj.foo = 1; console.log(Object.keys(myObj)); // console: ["foo"] /**/ 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 (let [key, value] of jane) { console.log(`${key}: ${value}`); } // first: Jane // last: Doe除了for...of循環(huán)以外,擴(kuò)展運(yùn)算符(...)、解構(gòu)賦值和Array.from方法內(nèi)部調(diào)用的,都是遍歷器接口。這意味著,它們都可以將 Generator 函數(shù)返回的 Iterator 對(duì)象,作為參數(shù)。
function* numbers () { yield 1 yield 2 return 3 yield 4 } // 擴(kuò)展運(yùn)算符 [...numbers()] // [1, 2] // Array.from 方法 Array.from(numbers()) // [1, 2] // 解構(gòu)賦值 let [x, y] = numbers(); x // 1 y // 2 // for...of 循環(huán) for (let n of numbers()) { console.log(n) } // 1 // 2
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/99505.html
摘要:以下展示它是如何工作的函數(shù)使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新的對(duì)象,并立即將其返回給調(diào)用者。在傳遞給構(gòu)造函數(shù)的函數(shù)中,我們確保傳遞給,這是一個(gè)特殊的回調(diào)函數(shù)。 本系列文章為《Node.js Design Patterns Second Edition》的原文翻譯和讀書(shū)筆記,在GitHub連載更新,同步翻譯版鏈接。 歡迎關(guān)注我的專欄,之后的博文將在專欄同步: Encounter的掘金專欄 知乎專欄...
摘要:最后,調(diào)用這個(gè)函數(shù),得到一個(gè)遍歷器對(duì)象并賦值給變量。值得注意的是如果函數(shù)內(nèi)部沒(méi)有部署代碼塊,那么遍歷器對(duì)象的方法拋出的錯(cuò)誤,將被外部代碼塊捕獲。 本文已同步至我的個(gè)人主頁(yè)。歡迎訪問(wèn)查看更多內(nèi)容!如有錯(cuò)誤或遺漏,歡迎隨時(shí)指正探討!謝謝大家的關(guān)注與支持! 一、什么是Generator函數(shù) Generator函數(shù)是ES6標(biāo)準(zhǔn)中提出的一種異步編程的解決方案。這種函數(shù)與普通函數(shù)最大的區(qū)別在于它可...
摘要:如果你已經(jīng)理解基礎(chǔ)可以直接跳過(guò)這部分和語(yǔ)法部分,直接看深入理解的部分。的參數(shù)可以傳入一個(gè)參數(shù),來(lái)作為上一次的表達(dá)式的返回值,就像我們上面說(shuō)的會(huì)讓等于。 這篇文章旨在幫你真正了解Generator,文章較長(zhǎng),不過(guò)如果能花時(shí)間耐心看完,相信你已經(jīng)能夠完全理解generator 為什么要用generator 在前端開(kāi)發(fā)過(guò)程中我們經(jīng)常需要先請(qǐng)求后端的數(shù)據(jù),再用拿來(lái)的數(shù)據(jù)進(jìn)行使用網(wǎng)頁(yè)頁(yè)面渲染等操...
摘要:的翻譯文檔由的維護(hù)很多人說(shuō),阮老師已經(jīng)有一本關(guān)于的書(shū)了入門(mén),覺(jué)得看看這本書(shū)就足夠了。前端的異步解決方案之和異步編程模式在前端開(kāi)發(fā)過(guò)程中,顯得越來(lái)越重要。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(shū)(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書(shū)的目的是以目前還在制定中的ECMASc...
摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。寫(xiě)一個(gè)符合規(guī)范并可配合使用的寫(xiě)一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來(lái)處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問(wèn)題描述 在開(kāi)發(fā)過(guò)程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過(guò)http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過(guò)...
閱讀 641·2023-04-26 01:42
閱讀 3251·2021-11-22 11:56
閱讀 2432·2021-10-08 10:04
閱讀 879·2021-09-24 10:37
閱讀 3154·2019-08-30 15:52
閱讀 1783·2019-08-29 13:44
閱讀 499·2019-08-28 17:51
閱讀 2177·2019-08-26 18:26