以前斷斷續續接觸到了一些ES6的知識,異步編程方面聽得比較多的就是Promise,直到最近比較系統地學習了ES6的新特性才發現Generator這個神奇的存在,它能夠實現一些史無前例的事情,讓我頓時對它充滿了興趣。javascript
JavaScript異步編程是爲解決JavaScript執行環境是「單線程」這個問題的。在JavaScript中,異步編程的使用很是頻繁,也常常會出現須要逐步完成多個異步操做的狀況。以前用回調函數實現異步編程若是碰到了這種問題就須要嵌套使用回調函數,異步操做越多,嵌套得就越深,這樣很是不利於代碼的維護,代碼閱讀起來也很困難。Generator函數是ES6提出的一種異步編程解決方案,它能夠避免回調的嵌套,可是它的用處可不只僅如此哦,待我細細道來。java
function* gen1() { yield 1; yield 'hello'; return true; } let g1 = gen1(); g1.next(); // Object {value: 1, done: false} g1.next(); // Object {value: "hello", done: false} g1.next(); // Object {value: true, done: true} g1.next(); // Object {value: undefined, done: true}
上面的代碼就定義了一個Generator函數,Generator函數的定義跟普通函數差很少,只是在function關鍵字後面加了一個星號。調用Generator函數後和普通函數不一樣的是,該函數並不當即執行,也不返回函數執行結果,而是返回一個指向內部狀態的generator對象,也能夠看做是一個遍歷器對象。而後必須調用該對象的next方法,讓函數繼續走下去,是指針移向下一個狀態。每當碰到yield語句,內部指針就停下來,直到下一次調用next()纔開始執行。
上面代碼調用了四次next方法,遍歷才結束。next方法會返回一個有兩個屬性的對象,value屬性的值爲當前yield語句的值,done屬性的值表示遍歷是否結束,即最後一次調用next方法時,再也碰不到yield或者return語句了。
星號寫在哪:
function關鍵字和函數名之間的星號寫在哪均可以,只要在二者之間便可,可是通常都採起我上面代碼的那種寫法。es6
上面說了那麼多,想必你們已經知道Generator函數是怎麼用的了,那麼Generator本質上究竟是個啥呢?Generator函數的理解有多種:編程
Generator函數能夠被理解成一個狀態機,裏面封裝了多種狀態,有興趣的同窗能夠去了解一下狀態機,操做系統的書裏都會講到。異步
Generator函數還能夠被理解成一個遍歷器對象生成器,它返回的遍歷器對象能夠依次遍歷Generator函數內部的每個狀態。這就是爲何以前說Generator函數不只是爲了解決回調函數嵌套問題。Generator函數是生成一個對象,可是調用的時候前面不能加new命令。異步編程
yield語句是Generator函數內部能夠暫停執行程序的語句,yield語句後面的值能夠是各類數據類型,字符串,整數,布爾值等等均可以。這裏主要想說說Generator函數中yield語句和return語句的區別。函數
從上面的例子能夠看出,函數不只是碰到yield語句纔會中止執行,碰到return語句也會中止執行。這很容易理解,無論怎樣Generator函數也是一個函數,碰到return語句必然會中止執行,返回值。那麼,二者的區別是什麼呢?先來看個例子:學習
function* gen2() { return true; yield 1; yield 'hello'; } let g2 = gen2(); g2.next(); // Object {value: true, done: true} g2.next(); // Object {value: undefined, done: true}
從上面例子能夠看出,當碰到return語句時,返回對象的done屬性值就爲true,遍歷結束,無論後面是否還有yield或者return語句。這種區別本質上是由於yield語句具有位置記憶功能而return語句則沒有該功能。this
Generator函數,無論內部有沒有yield語句,調用函數時都不會執行任何語句,只有當調用next(),內部語句纔會執行,只要調用next(),就會返回一個對象。yield語句只是函數暫停執行的一個標記。操作系統
function* gen3() { console.log('執行了麼?'); } let g3 = gen3(); // 沒有任何輸出 g3.next(); // 執行了麼? // Object {value: undefined, done: true}
注意:yield函數不能在普通函數中使用,不然會報錯。
除了yield語句,next方法也是Generator函數實現中很重要的特性。既然next()是一個函數,那麼這個函數能夠帶參數麼,固然能夠。上面的例子比較簡單,都只是一些單純的yield語句,其實Generator函數和普通函數同樣裏面是能夠進行各類複雜的計算和操做的,也能夠有各類循環語句,不只next方法能夠傳參數,Generator函數也是能夠傳參數的,立立刻例子:
function* gen4(a) { let b = yield (a + 1); return b * 2; } let g4 = gen4(1); g4.next(); // Object {value: 2, done: false} g4.next(); // Object {value: NaN, done: true} let g5 = gen4(1); g5.next(); // Object {value: 2, done: false} g5.next(3); // Object {value: 6, done: true}
上面例子中,Generator函數須要接收一個參數a,表面上變量b是用yield語句賦值了,可是遺憾的是這個賦值好像並無成功,當第二次調用next方法(沒有傳參數)時,返回的對象value值竟然爲NaN,而不是咱們想的 2 *(1+1)= 4。可是若是第二次調用next方法時,傳入一個參數3,返回對象的value值就爲6。這能夠說明兩點:
yield語句沒有返回值,或者老是返回undefined;
next方法若是帶上一個參數,這個參數就是做爲上一個yield語句的返回值。
注意:由於next方法表示上一個yield語句的返回值,因此必須有上一個yield語句的存在,那麼第一次調用next方法時就不能傳參數。第一個next只是用來啓動Generator函數內部的遍歷器,傳參也沒有多大意義。
雖然Generator函數和普通函數區別很大,可是Generator函數的實例也能夠繼承Generator函數的prototype對象上的方法。
function* gen5() {} gen5.prototype.say = function() { console.log('有generator?'); } let g6 = gen5(); g6.say(); // 有generator?
從上面代碼能夠看出,Generator函數返回的g6,繼承了gen5.prototype。
你們都知道普通函數都會有一個this對象,那麼Generator的this對象怎麼用呢?仍是例子更直觀:
function* gen6() { this.a = 1; } let g7 = gen6(); g7.a; // undefined
上面代碼中,Generator函數在this對象上添加了一個屬性a,g7實例並不能取到這個屬性。那麼怎麼讓Generator函數返回一個能夠正常使用this對象的實例呢?阮一峯老師提供了一種方法,首先,生成一個空對象,使用call方法綁定Generator函數內部的this。這樣,構造函數調用之後,這個空對象就是Generator函數的實例對象了。參考代碼在這:http://es6.ruanyifeng.com/#docs/generator
Generator函數返回的是一個遍歷器對象,那麼它在遍歷這方面確定有用武之地,下一次討論Iterator時候再總結吧。