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