ES6的不少特性都跟Generator扯上關係,並且實際用處比較廣, 包含了任何須要異步的模塊, 好比ajax, filesystem, 或者數組對象遍歷等均可以用到;css
Generator函數和普通的函數區別有兩個, 1:function和函數名之間有一個*號, 2:函數體內部使用了yield表達式;好比這樣:html
function* gen() { yield "1"; yield "2" }
這個玩意兒若是運行的話,會返回一個Iterator實例, 而後再執行Iterator實例的next()方法, 那麼這個函數纔開始真正運行, 並把yield後面的值包裝成固定對象並返回,直到運行到函數結尾, 最後再返回undefined; jquery
"use strict"; function* fibonacci() { yield 1; yield 2; } var it = fibonacci(); console.log(it); // "Generator { }" console.log(it.next()); // 1 console.log(it.next()); // 2 console.log(it.next()); //undefined
Generator函數返回的Iterator運行的過程當中,若是碰到了yield, 就會把yield後面的值返回, 此時函數至關於中止了, 下次再執行next()方法的時候, 函數又會從上次退出去的地方從新開始執行;es6
若是把yield和return一塊兒使用的話, 那麼return的值也會做爲最後的返回值, 若是return語句後面還有yield, 那麼這些yield不生效:ajax
function* gen() { yield 0; yield 1; return 2; yield 3; }; let g = gen(); console.log(g.next(),g.next(),g.next(),g.next()); //輸出:{ value: 0, done: false } { value: 1, done: false } { value: 2, done: true } { value: undefined, done: true }
咱們也不能在非Generator函數中使用yield,好比:數組
<script> var arr = [1, [[2, 3], 4], [5, 6]]; var flat = function* (a) { a.forEach(function (item) { if (typeof item !== 'number') { yield* flat(item); } else { yield item; } }) }; for (var f of flat(arr)){ console.log(f); } </script>
上面的demo由於callback是一個普通函數, 因此編譯的時候直接拋出錯誤提示, 咱們須要改爲在Generator的函數體中:瀏覽器
<script> var arr = [1, [[2, 3], 4], [5, 6]]; var flat = function* (a) { var length = a.length; for (var i = 0; i < length; i++) { var item = a[i]; if (typeof item !== 'number') { yield* flat(item); } else { yield item; } } }; for (var f of flat(arr)) { console.log(f); } </script>
或者有個更奇怪的方法,咱們把數組的forEach改爲Generator函數:微信
<script> var arr = [1, [[2, 3], 4], [5, 6]]; Array.prototype.forEach = function* (callback) { for(var i=0; i<this.length; i++) { yield* callback(this[i],i ,this[i]); } } var flat = function* (a) { yield* a.forEach(function* (item) { if (typeof item !== 'number') { yield* flat(item); } else { yield item; } }) }; for (var f of flat(arr)){ console.log(f); } </script>
並且Iterator的return的值不會被for...of循環到 , 也不會被擴展符遍歷到, 如下Demo的return 2 和yield 3徹底不生效了, 這個是要注意的;閉包
function* gen() { yield 0; yield 1; return 2; yield 3; }; let g = gen(); console.log([...g]); //輸出:[ 0, 1 ] for(let foo of g) { console.log( foo ); //輸出 0, 1 }
yield*這種語句讓咱們能夠在Generator函數裏面再套一個Generator, 固然你要在一個Generator裏面調用另外的Generator須要使用: yield* 函數() 這種語法, 都是套路啊:app
function* foo() { yield 0; yield 1; } function* bar() { yield 'x'; yield* foo(); yield 'y'; } for (let v of bar()){ console.log(v); };
Generator函數返回的Iterator執行next()方法之後, 返回值的結構爲:
{ value : "value", //value爲返回的值 done : false //done的值爲一個布爾值, 若是Interator未遍歷完畢, 他會返回false, 不然返回true; }
因此咱們能夠模擬一個Generator生成器, 利用閉包保存變量, 每一次執行next()方法, 都模擬生成一個{value:value,done:false}的鍵值對:
function gen(array){ var nextIndex = 0; return { next: function(){ return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true}; } }; }; var it = gen(["arr0", "arr1", "arr2", "arr3"]); console.log( it.next() ); console.log( it.next() ); console.log( it.next() ); console.log( it.next() ); console.log( it.next() );
再浪一點的話,咱們也能夠模擬一個對象的Iterator, 由於自己對象是沒有Iterator的, 咱們爲對象添加[Symbol.iterator]方法:
<script> var itObj = { 0:"00", 1:"11", 2:"22", 3:"33", length : 4, [Symbol.iterator]() { const _this = this; let index = 0; return { next() { if(index< _this.length) { return { value : _this[index++], done : false } }else{ return { value : undefined, done : true } } } } } }; console.log([...itObj]); </script>
若是給next方法傳參數, 那麼這個參數將會做爲上一次yield語句的返回值 ,這個特性在異步處理中是很是重要的, 由於在執行異步代碼之後, 有時候須要上一個異步的結果, 做爲下次異步的參數, 如此循環::
<script> function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true } </script>
上面的demo看懂了, next()方法的參數怎麼使用也就懂了;
若是執行Generator生成器的throw()方法, 若是在Iterator執行到的yield語句寫在try{}語句塊中, 那麼這個錯誤會被內部的try{}catch(){}捕獲 :
<script>
var g = function* () { try { yield; } catch (e) { console.log('內部捕獲0', e); } }; var i = g(); i.next(); //讓代碼執行到yield處; try { i.throw('a'); } catch (e) { console.log('外部捕獲', e); }
</script>
若是Interator執行到的yield沒有寫在try{}語句塊中, 那麼這個錯誤會被外部的try{}catch(){}語句塊捕獲;
<script>
var g = function* () { while(true) { try { yield; } catch (e) { console.log('內部捕獲', e); } } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('外部捕獲', e); }
</script>
若是執行Iterator的return()方法, 那麼這個迭代器的返回會被強制設置爲迭代完畢, 執行return()方法的參數就是這個Iterator的返回值,此時done的狀態也爲true:
<script> function* gen() { yield 0; yield 1; yield 2; yield 3; }; let g = gen(); console.log(g.return("heheda")); //輸出:{ value: 'heheda', done: true } </script.
Generator中的this就是誰調用它,那麼this就是誰, 咱們利用Reflect.apply能夠改變Generator的上下文:
function* gen() { console.log(this); yield 0; }; console.log(gen().next()); console.log(Reflect.apply(gen,"heheda").next());
Generator生成的Iterator,不但繼承了Iterator的原型, 也繼承了Generator的原型:
<script> function* gen() { console.log(this); yield 0; }; gen.prototype.foo = ()=> { console.log("foo"); } let g = gen(); console.log(Reflect.getPrototypeOf(g) === gen.prototype); //輸出:true </script>
因此若是要讓生成器繼承方法, 咱們能夠這樣, 感受好酷, 可是Generator內部的this是指向原型的, 也就是說已經把原型污染了:
<script> function* gen() { this.bar = "bar"; yield 0; }; gen.prototype.foo = ()=> { console.log("foo"); } let g = Reflect.apply(gen, gen.prototype,[]); console.log(g.next()); //輸出:Object {value: 0, done: false} console.log(g.bar); //輸出:bar </script>
ajax的異步處理, 利用生成器的特性,不但能夠用於ajax的異步處理, 也可以用於瀏覽器的文件系統filesystem的異步:
<html> <head> <meta charset="utf-8"> <script src="//cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js"></script> </head> <body> <script> "use strict"; function* main() { var result = yield request("http://www.filltext.com?rows=10&f={firstName}"); console.log(result); //do 別的ajax請求; } function request(url) { var r = new XMLHttpRequest(); r.open("GET", url, true); r.onreadystatechange = function () { if (r.readyState != 4 || r.status != 200) return; var data = JSON.parse(r.responseText); //數據成功返回之後, 代碼就可以繼續往下走了; it.next(data); }; r.send(); } var it = main(); it.next(); console.log("執行到這兒啦"); </script> </body> </html>
以上代碼中的console.log("執行到這兒啦");先被執行了, 而後纔出現了ajax的返回結果, 也就說明了Generator函數是異步的了;
function* iterEntries(obj) { let keys = Object.keys(obj); for (let i=0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]]; } } let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) { console.log(key, value); //輸出:foo 3 , bar 7 }
https://davidwalsh.name/es6-generators
https://davidwalsh.name/es6-generators-dive
https://davidwalsh.name/async-generators
https://davidwalsh.name/concurrent-generators
http://www.2ality.com/2015/03/es6-generators.html
http://es6.ruanyifeng.com/#docs/generator
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
做者: NONO
出處:http://www.cnblogs.com/diligenceday/
QQ:287101329
微信:18101055830