摘要:第二次同理,遇到了第二個(gè)函數(shù)會(huì)停下來,輸出的遍歷器對(duì)象值為,的值依然是。比如返回的遍歷器對(duì)象,都會(huì)有一個(gè)方法,這個(gè)方法掛在原型上。這三個(gè)函數(shù)共同的作用是讓函數(shù)恢復(fù)執(zhí)行。
Generator的語法
簡(jiǎn)介generator的英文意思是生成器
關(guān)于Generator函數(shù),我們可以理解成是一個(gè)狀態(tài)機(jī),里面封裝了多種不同的狀態(tài)。
function* gener(){ yield "hello"; yield "world"; return "ending"; } var g = gener(); // g是一個(gè)遍歷器對(duì)象 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è)Generator函數(shù),這個(gè)函數(shù)有兩個(gè)地方與其他函數(shù)不同
function后邊跟一個(gè)*
函數(shù)內(nèi)部有一個(gè)關(guān)鍵字yield。yield表示定義一個(gè)狀態(tài),所以上邊的函數(shù)其實(shí)有三個(gè)狀態(tài)。
調(diào)用Generator函數(shù)和普通函數(shù)一樣,都是后面跟圓括號(hào),不過調(diào)用Generator函數(shù),函數(shù)并不執(zhí)行,返回的也不是正常的返回結(jié)果,而是一個(gè)指向內(nèi)部狀態(tài)的指針函數(shù),也就是Iterator這個(gè)遍歷器對(duì)象。
來剖析一下上面的代碼:
上面代碼一共執(zhí)行了四次。調(diào)用next()便會(huì)執(zhí)行一次。每次代碼執(zhí)行到y(tǒng)ield的時(shí)候就會(huì)停下來,輸出一個(gè)對(duì)象,對(duì)象的value值就是yield后邊的值,done這個(gè)值表示遍歷是否結(jié)束,這個(gè)時(shí)候?yàn)閒alse。
第二次同理,遇到了第二個(gè)yield函數(shù)會(huì)停下來,輸出的遍歷器對(duì)象value值為"world",done的值依然是false。
第三次,當(dāng)代碼遇到了return語句(如果沒有return語句,就一直執(zhí)行到函數(shù)結(jié)束)這個(gè)時(shí)候返回的遍歷器對(duì)象的value的值,就是return后邊跟的表達(dá)式的值,這個(gè)時(shí)候因?yàn)楸闅v結(jié)束了,所以done的值就變成了true.
第四次,因?yàn)楹瘮?shù)已經(jīng)return,next()返回的對(duì)象的value就是undefined,done值為true
yield的邏輯理論上,yield提供了一種函數(shù)可以暫停的機(jī)制。而暫停的表達(dá)式就是yield。
當(dāng)代嗎執(zhí)行到y(tǒng)ield語句的時(shí)候,會(huì)暫停不會(huì)立即執(zhí)行,并且把yield后面表達(dá)式的值當(dāng)做返回對(duì)象的value屬性值返回。
當(dāng)下一次調(diào)用next()方法的時(shí)候,會(huì)從當(dāng)前暫停的位置繼續(xù)執(zhí)行,知道遇到下一個(gè)yield或者return語句,返回其后面表達(dá)式的值。
如果都沒有,那么一直執(zhí)行到代碼結(jié)束。這個(gè)時(shí)候返回對(duì)象value值是undefined。
當(dāng)遍歷結(jié)束的時(shí)候,done的值會(huì)從false變成true.
yield和return的區(qū)別?
兩者相同點(diǎn):都是返回跟在其后面的表達(dá)式的值。
兩者不同點(diǎn):yield有記憶功能,函數(shù)執(zhí)行完以后會(huì)記錄下來在從記錄的位置繼續(xù)執(zhí)行;return并不具有記憶功能,從這返回以后函數(shù)不會(huì)在執(zhí)行,僅僅執(zhí)行一次。而Generator可以返回多個(gè)值,返回一系列的值。
function* fun(){ console.log("執(zhí)行了!") } var gen = fun(); setTimeout(function(){ gen.next(); },3000)
這個(gè)console要到3s后才執(zhí)行。
另外,yield語句只能在Generator函數(shù)里面。所以,yield也不能放到forEach函數(shù)里,也不能放到map函數(shù)里。
function funerr(){ yield 123+321 } //報(bào)錯(cuò)
再另外,如果yield語句在另外一個(gè)表達(dá)式里,必須在圓括號(hào)里。
function* fun(){ console.log("hello"+yield) // 語法錯(cuò)誤 console.log("hello"+(yield)) //正確 }
再再另外,yield作為函數(shù)參數(shù)或者賦值表達(dá)式的右邊,可以不用加括號(hào)。
next方法的參數(shù)yield本身并沒有返回值,但是在next的參數(shù)里可以帶一個(gè)參數(shù),這個(gè)參數(shù)表示上一個(gè)yield語句的返回值。
// next()的參數(shù) function *f() { for(var i=0;true;i++){ var reset = yield i; if(reset) { i = -1 } } } var f = f() console.log(f.next()) //{value:0,done:false} console.log(f.next()) //{value:1,done:false} console.log(f.next()) //{value:2,done:false} console.log(f.next(true)) //{value:0,done:false}
前邊幾次輸出,這個(gè)時(shí)候reset的值是undefined,所以,i的值一次增加,當(dāng)next函數(shù)傳一個(gè)true,代表上一次的yield的值是true,那么這個(gè)時(shí)候i的值是-1,下一次循環(huán)從i等于-1開始.
Generator函數(shù),從暫停狀態(tài)到恢復(fù)運(yùn)行,上下文是不變的。通過next方法的參數(shù),就有辦法在函數(shù)運(yùn)行之后重新往函數(shù)里注入值,也就是在函數(shù)不同階段注入不同的值,
function* foo(x) { var y = yield (x+2) var z = (yield ((y+3) *2)) return x+y+z } var foo1 = foo(5) console.log(foo1.next()) //{value:7,done:false} console.log(foo1.next()) //{value:NaN,done:false} console.log(foo1.next()) // {value:NaN,done:true} var foo2 = foo(5) console.log(foo2.next()) // {value:7,done:false} x:5 y:7 console.log(foo2.next(2)) //{value:10,done:false} x:5 y:2 console.log(foo2.next(3)) // {value:10,done:true} x:5 y:2 z:3
執(zhí)行第二個(gè)next方法的時(shí)候,這個(gè)時(shí)候沒有傳入值,所以這個(gè)時(shí)候,y的值是undefined。所以。2 * undefined是NaN。
這個(gè)地方不是太好理解,可以再舉一個(gè)例子
function* foo(x) { var y = 2 * (yield (x+2)) //之前說過的,yield作為表達(dá)式一定要在括號(hào)里 var z = (yield (y+3) *2) return x+y+z } var foo2 = foo(5) log(foo2.next()) log(foo2.next(2)) log(foo2.next(3))用for...of代替next方法
每次總是調(diào)用next方法太過麻煩。for...of循環(huán)可以自動(dòng)遍歷Generator函數(shù)生成的Itearator對(duì)象,不需要調(diào)用next方法。
function* bar () { yield 3; yield 4; yield 1; yield 7; yield 9; return 0; } for (var i of bar()){ console.log(i) // 3,4,1,7,9 }
注意,上面的return語句并不在循環(huán)中,因?yàn)楸闅v到done為true的時(shí)候就會(huì)停止,所以,不會(huì)輸出0.
理論上,實(shí)現(xiàn)了Iterator遍歷器接口的擴(kuò)展運(yùn)算符(...),結(jié)構(gòu)賦值,Array.from()內(nèi)部調(diào)用的,都可以將Generator的返回值作為參數(shù)。比如:
function* foo(){ yield:1; yield:3; return 9; yield:6 } [...foo()] // [1,3] Array.from(foo()) // [1,2] let [x,y] = foo() //x=1 y=2Generator.prototype.throw()
Generator返回的遍歷器對(duì)象,都會(huì)有一個(gè)throw方法,這個(gè)方法掛在原型上。作用是在外部拋出在函數(shù)內(nèi)部捕獲的錯(cuò)誤
function* gg() { try { yield ; } catch (e){ console.log("內(nèi)部錯(cuò)誤",e) } } var gg = gg() gg.next(); try { gg.throw("a") gg.throw("b") } catch (e){ console.log("外部錯(cuò)誤",e) }
在外部的try語句中,會(huì)連續(xù)拋出兩個(gè)錯(cuò)誤,第一個(gè)錯(cuò)誤會(huì)被內(nèi)部捕獲,但是到了第二次的時(shí)候,因?yàn)閮?nèi)部的catch語句已經(jīng)執(zhí)行過了,所以就不會(huì)再次執(zhí)行,所以錯(cuò)誤會(huì)被外部的catch捕獲。throw方法接受一個(gè)參數(shù),可以再catch里輸出,不過一般還是catch輸出一個(gè)Error對(duì)象。
var g = function* () { try { yield 8; } catch (e) { console.log(e); } }; var i = g(); console.log(i.next()) i.throw(new Error("出錯(cuò)了!")); console.log(i.next(7))
如果在Generator函數(shù)內(nèi)部沒有部署try/catch,那么直接會(huì)在外部拋出錯(cuò)誤,如果內(nèi)外部都沒有try/catch。那么函數(shù)直接錯(cuò)誤中斷執(zhí)行。
如果函數(shù)內(nèi)部拋出了錯(cuò)誤,并不影響接下來yield或者return的執(zhí)行。
Generator.prototype.return()這個(gè)函數(shù)的作用就是return出一個(gè)值,并且終結(jié)Generator函數(shù)的執(zhí)行。
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return("foo") // { value: "foo", done: true } g.next() // { value: undefined, done: true }next() throw() return()
這三個(gè)函數(shù)共同的作用是讓Generator函數(shù)恢復(fù)執(zhí)行。
next()就是讓yield賦一個(gè)值,如果next()有參數(shù),即是給yield傳入一個(gè)值,如果沒有參數(shù),就是undefined。
throw()是把yield表達(dá)式替換成一個(gè)throw語句。
return()是把yield表達(dá)式替換成一個(gè)return語句。
理論上,三個(gè)函數(shù)都是在做同樣一件事情。
yield* 表達(dá)式如果在一個(gè)Generator函數(shù)里調(diào)用另外一個(gè)Generator函數(shù),默認(rèn)是沒有效果的。
function* foo(){ yield "xx"; yield "yy"; } function* bar(){ yield "aa"; foo(); yield "bb"; } for(var i of bar()){ console.log(i) // aa bb }
如果想要在bar函數(shù)中執(zhí)行foo函數(shù),需要改寫一下bar函數(shù)
function bar(){ yield "aa"; yield* foo() yield "bb" } 輸出的會(huì)是"aa" "xx" "yy" "bb"作為對(duì)象屬性的Generator函數(shù)
如果一個(gè)對(duì)象的屬性是Generator函數(shù),可以簡(jiǎn)寫成:
let obj = { * foo(){ .... } } //等價(jià)于 let obj = { foo : function* (){ .... } }Generator中的this
Generator函數(shù)返回一個(gè)遍歷器對(duì)象,這個(gè)遍歷器對(duì)象是Generator函數(shù)的實(shí)例,當(dāng)然也就繼承了函數(shù)原型上的那些方法。
function* foo(){ yield "xx" } let f = foo() f00.prototype.hello = function(){ console.log(123) } f instanceof foo // true f.hello() //123
所以,代碼可以看出,f是foo的實(shí)例,同時(shí)可以調(diào)用foo原型上重寫的方法。
但是:
function* foo(){ this.a = 10 } let f = foo() f.a //undefined new foo() //報(bào)錯(cuò),foo is not a constructor
因?yàn)閒oo()返回的是一個(gè)遍歷器對(duì)象,而不是this.
同時(shí),Generator函數(shù)也不能和new一起使用。
那如何既能調(diào)用next()又能獲取this呢。有一個(gè)變通的方法。用call綁定內(nèi)部的this
function* foo(){ this.a = 1; yield this.b = 2; yield this.c = 3; } let f = foo.call(F.prototype) f.next() {value:2,done:false} f.next() {value:3,done:true} f.next() {valye:undefined,done:true} f.a 1 f.b 2 f.3 3
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/88958.html
摘要:更好的語義和分別表示異步和等待,比起和更容易理解。前邊聲明關(guān)鍵字,表示內(nèi)部有內(nèi)部操作,調(diào)用函數(shù)會(huì)返回一個(gè)對(duì)象。等價(jià)于其中函數(shù)就是自動(dòng)執(zhí)行器。 async函數(shù) 定義 async函數(shù)其實(shí)就是之前說過的Generator的語法糖,用于實(shí)現(xiàn)異步操作。它是ES2017的新標(biāo)準(zhǔn)。 讀取兩個(gè)文件: const fs = require(fs) const readFile = function(f...
摘要:傳統(tǒng)的異步方法回調(diào)函數(shù)事件監(jiān)聽發(fā)布訂閱之前寫過一篇關(guān)于的文章,里邊寫過關(guān)于異步的一些概念。內(nèi)部函數(shù)就是的回調(diào)函數(shù),函數(shù)首先把函數(shù)的指針指向函數(shù)的下一步方法,如果沒有,就把函數(shù)傳給函數(shù)屬性,否則直接退出。 Generator函數(shù)與異步編程 因?yàn)閖s是單線程語言,所以需要異步編程的存在,要不效率太低會(huì)卡死。 傳統(tǒng)的異步方法 回調(diào)函數(shù) 事件監(jiān)聽 發(fā)布/訂閱 Promise 之前寫過一篇關(guān)...
摘要:在這里看尤雨溪大神的這篇小短文,非常精簡(jiǎn)扼要地介紹了當(dāng)前常用的。根據(jù)尤雨溪大神的說法,的也只是的語法糖而已。對(duì)象有三種狀態(tài),,。對(duì)象通過和方法來規(guī)定異步結(jié)束之后的操作正確處理函數(shù)錯(cuò)誤處理函數(shù)。方便進(jìn)行后續(xù)的成功處理或者錯(cuò)誤處理。 最近在寫一個(gè)自己的網(wǎng)站的時(shí)候(可以觀摩一下~Colors),在無意識(shí)中用callback寫了一段嵌套了5重回調(diào)函數(shù)的可怕的代碼。回過神來的時(shí)候被自己嚇了一跳,...
摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:返回值是一個(gè)對(duì)象,它的第一個(gè)屬性是后面表達(dá)式的值或者的值第二個(gè)屬性表示函數(shù)是否執(zhí)行完成。真正的業(yè)務(wù)邏輯確實(shí)是用同步的方式寫的。 開始前 我們從來沒有停止過對(duì)javascript語言異步調(diào)用方式的改造,我們一直都想用像java那樣同步的方式去寫異步,盡管Promise可以讓我們將異步回調(diào)添加到then方法中,但是這種調(diào)用方式仍然不那么優(yōu)雅,es6 中新增加了generator,我們可以通...
閱讀 3732·2021-10-14 09:43
閱讀 3316·2021-08-25 09:38
閱讀 614·2019-08-30 15:55
閱讀 1353·2019-08-30 13:05
閱讀 2246·2019-08-29 16:05
閱讀 511·2019-08-29 12:58
閱讀 2799·2019-08-29 12:34
閱讀 3247·2019-08-26 12:15