上節中忘記講:Iterator接口和Generator函數的關係了,Symbol.iterator方法的最簡單的實現就是經過Generator函數:node
let myIterable = { [Symbol.iterator]:function* (){ yield 1; yield 2; yield 3; } } let aa = [...myIterable]; aa //[1, 2, 3]
用Generator函數寫Symbol.iterator方法幾乎不用部署任何代碼,只要yield命令給出每一步的返回值便可。接下來開始寫Generator函數吧,貌似沒有promise好用,看下圖,Await/Async函數是最膩害的,因此掌握最強的方法,你就能夠所向披靡了。編程
在瀏覽知乎的中一個有關node.js是用來作什麼的時候,看到有關ES6的新特性,而後就寫個博客吧!
Part One:promise數組
Part Two:Generatorpromise
Generator函數是個狀態機,封裝多個內部狀態。執行Generator函數會返回一個遍歷器對象。安全
function* hello(){ yield 'hello'; yield 'world'; return 'ending'; } var hw = hello();
hw.next(); //{value: "hello", done: false}
hw.next() //{value: "world", done: false}
hw.next() //{value: "ending", done: true}
hw.next() //{value: undefined, done: true}
這裏定義了一個generator函數,變量hw是一個指向hello函數內部狀態的指針對象。函數的調用必須調用遍歷器對象的next方法,使得指正移向下一個狀態。調用next方法時候,內部指針從函數頭部或者上一次停下來的地方開始執行。Generator函數是分段執行的,yield表達式是暫停執行的標記,next是恢復執行。數據結構
yield表達式:異步
在Generator函數內部,yield表達式只是暫停的標識。函數式編程
yield和return的類似之處在於都能返回緊跟在語句後面的表達式的值,區別在於每次遇到yeild,函數暫停執行,下一次再從該位置繼續向後執行。return不具有位置記憶的功能。Generator函數能夠返回一系列的值,由於內部能夠有多個yield。(Generator在英文中是「生成器」的意思)函數
若是Generator內部不用yield,就變成了一個單純的暫緩執行的函數。this
yield只能用在Generator函數內部,其餘地方是會報錯的!!!
在用for/of進行遍歷Generator時候,不須要再調用next方法,例如最上面的例子:Generator和Iterator接口的關係時,舉個栗子:
function* foo(){ yield 1; yield 2; yield 3; return 4; } for(var i of foo()){ console.log(i) } //1 2 3
一旦next方法的返回對象的done屬性爲true時,for/of循環就會停止,且不包含返回值,因此上面的return語句返回的4不包含在循環中。
這裏使用Generator函數寫了一個斐波那契數列的方法,遍歷時候將小於1000的數字打印出來:
function* fibonacci(){ let [prev,curr] = [0,1]; for(;;){ [prev,curr] = [curr,prev+curr]; yield curr; } } for(let n of fibonacci()){ if(n>1000) break; console.log(n); }
如何遍歷一個沒有Iterator接口的對象呢?上一節中給對象加上Symbol.iterator屬性就能夠,而在Generator當中呢?
function* objectEntires(obj){ let propKeys = Reflect.ownKeys(obj); for(let propKey of propKeys){ yield [propKey,obj[propKey]]; } } let jane = {first: 'Jane',last: 'Ostin'}; for(let [key,value] of objectEntires(jane)){ console.log(`${key}->${value}`) } VM4211:11 first->Jane VM4211:11 last->Ostin
或者
函數當中涉及到Reflect,有時間寫一篇有關Reflect的博文~~~
拓展運算符(...),解構賦值和Array.from()等都是遍歷器接口,能夠將Generator函數做爲參數。
function* numbers(){ yield 1; yield 2; return 3; yield 4; } let aa = [...numbers()] let bb = Array.from(numbers()); let [x,y] = numbers(); aa //[1, 2] bb //[1, 2] x //1 y //2
這裏有個須要特別注意的點:一旦Generator執行過程當中拋出錯誤,且沒有被內部捕獲,就不會再執行下去了,若是還調用next方法,value就是undefined,done就是true了。即JavaScript引擎認爲這個Generator已經運行結束了。
注意:在Generator內部調用一個Generator函數是不會起做用的,因此就須要用到yield*表達式,用於在一個Generator函數 裏面執行另一個Generator函數。
function* foo(){ yield 'a'; yield 'b'; } function* bar(){ yield 'x'; yield* foo(); yield 'y'; } [...bar()] //["x", "a", "b", "y"]
若是不用yield* 而使用yield後面跟一個Generator函數的話,返回的就是一個遍歷器對象了。在使用yield*時候(Generator裏面沒有return語句),就相似於for/of的簡寫形式。在有return語句時候呢?
任何數據結構只要有Iterator接口,就能夠被yield*遍歷。
let read = (function* (){ yield 'hello'; yield* 'hello'; }()) [...read] //["hello", "h", "e", "l", "l", "o"]
function* AA(){ yield 1; return 2; } function* BB(){ yield 3; let re = yield* AA(); console.log(re) yield 4; } [...BB()] //2 // [3, 1, 4]
在運行內部有return的Generator函數時候,若是將它賦值爲一個變量指針,則打印結果是會有return的結果輸出。
出個題:若是遍歷二維數組?
const tree = ['a',['b','c'],['d','e']]; function* iterTree(arr){ for(let item of arr){ if(Array.isArray(item)){ yield* iterTree(item); }else{ yield item; } } } [...iterTree(tree)] // ["a", "b", "c", "d", "e"]
若是對象的屬性是Generator函數,能夠進行簡寫:
let obj = { * myMethod(){ yield 1; yield 2; } } [...obj.myMethod()] //[1, 2] 相似於==》 let obj = { myMethod:function* (){ yield 1; yield 2; } } [...obj.myMethod()] //[1, 2]
注意:Generator函數和普通構造函數的區別?
相同點:實例化後的Generator函數對象,會繼承Generator函數的原型上的方法,這點跟構造函數蕾西。
區別:Generator函數返回的是遍歷器對象,而不是this對象,因此函數上的固有方法,實例是不繼承的,也不能new一個Generator對象,會報錯。
如何將Generator函數返回一個正常的實例對象,既可使用next方法,也能夠得到正常的this對象?例如:F是一個Generator函數,能夠將F內部的this對象綁定obj對象,而後調用next方法,返回Iterator對象,給obj上面添加屬性,將全部內部的屬性都綁定在obj對象上面,所以obj對象也就是F的實例了。
如何將obj和f統一?
能夠將F的this指向它本身的原型對象。以下:
function* F(){ this.a = 1; yield this.b = 2; yield this.c = 3; } var f = F.call(F.prototype); f.next(); f.next(); f.next(); console.log(f.a,f.b,f.c); //1,2,3
就是將Generator函數裏面的this指向了它自己的原型對象上面,在調用了next方法以後給原型上面添加屬性。
如何讓f是能夠用構造函數new出來的對象,還可使用Generator函數的next方法?
function* F(){ this.a = 1; yield this.b = 2; yield this.c = 3; } function gen(){ return F.call(F.prototype); } var f = new gen(); f.next(); f.next(); f.next(); console.log(f.a,f.b,f.c); VM910:14 1 2 3
就是真正的再新建一個構造函數,裏面return F的實例,而後就能夠new一個對象並使用Generator的方法,還有原型上面的屬性了。
用Generator函數 實現一個狀態機就更加簡潔化,並且比較安全(狀態不會給非法篡改),更加符合函數式編程的思想。
var clock = function* (){ while(true){ console.log("Tick!"); yield; console.log("Tock!") yield; } } var c = clock(); c.next() //Tick! c.next() //Tock! c.next() //Tick!
Generator能夠暫停函數執行,返回任意表達式的值,應用場景:
1,處理異步操做,改寫回調函數
2,能夠經過Generator函數逐步讀取文本文件
3,控制流管理,多部操做很是耗時,採用回調函數的方式,函數嵌套的模式。若是是promise的話,多是回調在then裏面的嵌套,若是用Generator函數的話:
function scheduler(task){ var taskObj = task.next(task.value); if(!taskObj.done){ task.value = taskObj.value; scheduler(task); } } function* longRunningTask(v1){ try{ var v2 = yield step1(v1); var v3 = yield step2(v2); var v4 = yield step3(v3); var v5 = yield step4(v4); }catch(e){ console.log(`ERR${e}`) } } scheduler(longRunningTask(initialValule))
那麼異步的處理方法呢??下一節講Await/Async方法,本節的Generator講太多,篇幅過長~~~