ES6生成器基礎

    ES6引進的最使人興奮的特性就是一種新的函數生成方式,稱爲生成器(generator)。名稱有點奇怪,可是第一眼看上去行爲更加奇怪。文章主要介紹生成器如何工做,而後讓你明白爲何他們對於將來的JS會有很大的影響。es6

完成運行編程

    首先看看生成器和普通函數有什麼不一樣。不管你是否已經意識到,關於你的函數,老是能夠很基本的假設一些東西:一但函數開始運行,它老是在其餘JS代碼能夠運行前運行完畢。例子:設計模式

setTimeout(function(){
    console.log("Hello World");
},1);

function foo() {
    // NOTE: don't ever do crazy long-running loops like this
    for (var i=0; i<=1E10; i++) {
        console.log(i);
    }
}

foo();
// 0..1E10
// "Hello World"

    在這裏,完成for循環會花至關長的時間,比1毫秒長不少,可是咱們的計時器回調函數console.log("Hello World")並不能在foo()函數運行期間打斷他,因此它卡住了,排在最後面(在循環後面),耐心地等待輪到他。數組

    可是若是foo()能夠被打斷是什麼樣的?難道不會對程序形成嚴重破壞嗎?多線程

    這就是噩夢般的多線程編程挑戰,可是很幸運,在JS領土中,不用擔憂這種狀況,由於JS老是單線程的(在任何給定的時間內,只有一條命令\函數執行)。併發

    注:Web開發人員對於一部分JS程序,能夠做爲一個獨立的線程來運行,徹底與主JS線程並行。這樣不會引發多線程並行到程序中的理由,是兩個線程只能經過普通異步事件和彼此交互,而異步事件遵循的是一次只執行一條的原則。異步

運行...中止...運行函數

    經過ES6生成器,有了一種不一樣的函數,這種函數能夠停在中間,一次或者屢次,事後又恢復,容許其餘代碼在這個中止期運行。oop

    若是閱讀過關於併發或者多線程的任何東西,會看到「合做」期,即一個進程(一個函數)本身選擇何時容許打斷,以便它能夠和其餘代碼「合做」。這一律念與"先發"造成對比,代表進程/函數可能違背其意願被打斷。this

    ES6生成器函數在併發中是「合做」的。在生成器函數體內,可使用新的yield關鍵字從內部暫停本身。沒有什麼能夠從外部暫停一個生成器,函數在內部遇到yield時能夠暫停本身。

    可是,生成器函數包含yield暫停本身的時候,它不能恢復本身。必須使用外部控制來重啓生成器。後續會介紹這個怎麼發生的。

    因此,一個生成器函數能夠中止,被從新啓動,能夠自定義次數。事實上,可使用一個無限循環(相似while (true) { .. })來指定一個生成器函數。在日常的JS程序裏面這多是瘋狂的或者是一個錯誤,對於生成器函數來講是很理智的,有時就是你想要作到的!

    更重要的,這個中止和開始並非在生成器函數執行中的一個控制,容許2通道消息傳入和傳出生成器。在日常的函數中,在開始能夠獲取到參數,在結尾處得到一個return值。利用生成器函數,使用每一個yield向外輸出消息,使用每一個start回送消息。

語法!

    讓咱們看看這個新的、使人興奮的生成器函數的語法。

    首先,新的聲明語法:

function *foo() {
    // ..
}

    注意到這裏的*了麼?這就是全新的有點奇怪的格式。在其餘的一些語言裏面,看上去很像糟糕的函數返回指針。可是不要疑惑!這只是一個標記生成器函數的方式。
    你也許在其餘文章裏面看到過function* foo(){ } 來替代 function *foo(){ } (*的位置不一樣),這兩種狀況都是合法的,可是最近以爲function *foo() { }更明確一些,也就是這裏用到的方式。

    如今咱們討論一下生成器函數的內容。大多數狀況下生成器函數只是普通的JS函數。在生成器函數內部基本沒什麼新的語法可學的。

    咱們主要必須玩會的新玩具,在上面提到過,就是yield關鍵字。yield__被稱爲"yield表達式"(不是聲明)是由於當啓動生成器的時候,咱們會傳遞一個值進去,傳遞進去的值會是yield表達式的計算結果。例如:

function *foo() {
    var x = 1 + (yield "foo");
    console.log(x);
}

    yield "foo"表達式會在暫停生成器函數的時候將foo值傳出來,當生成器從新啓動的時候,傳遞進來的值會是那個表達式的結果,會加1指給x.
    看到2通道的交互了麼?你將"foo"傳出,暫停了本身,在事後的某時間點(多是當即,也多是距離如今很長的一段時間),生成器函數會從新啓動會傳遞一個回送值。

    在任何表達式的位置,在表達式或聲明中能夠單獨使用yield。yield有一個假定的值undefined:

// note: `foo(..)` here is NOT a generator!!
function foo(x) {
    console.log("x: " + x);
}

function *bar() {
    yield; // just pause
    foo( yield ); // pause waiting for a parameter to pass into `foo(..)`
}

生成器迭代器

    迭代器是一種特殊的用法,事實上是一種設計模式,當使用next()進入一系列預選的值中的時候,想象下在一個有5個值的數組上使用迭代器的時候,第一個next()調用會返回1,第二個next()調用會返回2,以此類推。等到全部值被返回,next()會返回null或false或者其餘標誌位來表示你已經迭代完了全部的值。

    從外部控制生成器函數的方法是使用生成器迭代器構建和交互。聽起來比實際複雜些。例子:

function *foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
}

    進入*foo()生成器的值中,須要構建一個迭代器:

var it = foo();

    因此使用日常的方法調用生成器函數不會執行它的任何一部分。

    這看起來有點奇怪。你也許想要知道,爲何不是var it = new foo()。這語法後的緣由很複雜已經超出了這裏的討論範圍。

    因此,如今開始迭代咱們的生成器函數,咱們只需:

var message = it.next();

    這會從yield 1聲明返回1,但那不是咱們僅僅獲得的。

console.log(message); // { value:1, done:false }

    實際上咱們從每一個next()調用獲得了一個對象,它有一個yield傳出值的value屬性,done是一個表示生成器函數是否運行完成的布爾變量。

    繼續看迭代器:

console.log( it.next() ); // { value:2, done:false }
console.log( it.next() ); // { value:3, done:false }
console.log( it.next() ); // { value:4, done:false }
console.log( it.next() ); // { value:5, done:false }

    有趣的是,done在咱們獲得5這個值的時候仍然是false。這是由於,技術上,生成器函數沒有完成。咱們不得不調用最後一個next()調用,若是咱們傳入一個值,它必須做爲yield5表達式的結果。只有那時生成器函數完成了。
    因此,如今:

console.log( it.next() ); // { value:undefined, done:true }

    生成器函數的最後的結果是咱們調用完成了,可是沒有結果給出來。
    你也許想知道,在生成器函數內能夠用return麼,若是用了,那個值會在value屬性裏面輸出麼?

    是的...

function *foo() {
    yield 1;
    return 2;
}

var it = foo();

console.log( it.next() ); // { value:1, done:false }
console.log( it.next() ); // { value:2, done:true }

    不是...

    在生成器內依賴return不是一個好的作法,由於使用for-of循環迭代生成器的時候,最後return的值會被忽略。

    爲下降風險,咱們迭代生成器的時候看看傳遞進去和出來信息:

function *foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var it = foo( 5 );

// note: not sending anything into `next()` here
console.log( it.next() );       // { value:6, done:false }
console.log( it.next( 12 ) );   // { value:8, done:false }
console.log( it.next( 13 ) );   // { value:42, done:true }

    能夠看到,仍然能夠經過內部的foo(5)迭代器實例化調用向內傳遞參數,就像普通函數同樣,使x爲5.
    第一個next()調用,咱們沒有傳遞任何參數。爲何?由於沒有yield表達式來接收傳遞的參數。

 

英文原文:http://davidwalsh.name/es6-generators

相關文章
相關標籤/搜索