文章出處

  ES6的很多特性都跟Generator扯上關系,而且實際用處比較廣, 包含了任何需要異步的模塊, 比如ajaxfilesystem, 或者數組對象遍歷等都可以用到;

  Generator的使用:

  Generator函數和普通的函數區別有兩個, 1:function和函數名之間有一個*號, 2:函數體內部使用了yield表達式;比如這樣:

function* gen() {
    yield "1";
    yield "2"
}

   這個玩意兒如果運行的話,會返回一個Iterator實例, 然后再執行Iterator實例的next()方法, 那么這個函數才開始真正運行, 并把yield后面的值包裝成固定對象并返回,直到運行到函數結尾, 最后再返回undefined; 

"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

 

  

  yield

  Generator函數返回的Iterator運行的過程中,如果碰到了yield, 就會把yield后面的值返回, 此時函數相當于停止了, 下次再執行next()方法的時候, 函數又會從上次退出去的地方重新開始執行;

  如果把yieldreturn一起使用的話, 那么return的值也會作為最后的返回值, 如果return語句后面還有yield, 那么這些yield不生效:

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 2yield 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*

  yield*這種語句讓我們可以在Generator函數里面再套一個Generator, 當然你要在一個Generator里面調用另外的Generator需要使用: yield* 函數() 這種語法, 都是套路啊:

function* foo() {
    yield 0;
    yield 1;
}
function* bar() {
    yield 'x';
    yield* foo();
    yield 'y';
}
for (let v of bar()){
    console.log(v);
};

 

  next()方法

  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>
View Code

 

  next()方法的參數

  如果給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()方法的參數怎么使用也就懂了;

  throw方法()

  如果執行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>

 

  return()方法:

  如果執行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和他的原型

  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函數是異步的了;   

  

 

  利用Generator函數,可以在任意對象上部署iterator接口:

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 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()