你不懂JS:ES6與未來 第三章:組織(上)

官方中文版原文鏈接

感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大獎(jiǎng):點(diǎn)擊這里領(lǐng)取

編寫JS代碼是一回事兒,而合理地組織它是另一回事兒。利用常見的組織和重用模式在很大程度上改善了你代碼的可讀性和可理解性。記住:代碼在與其他開發(fā)者交流上起的作用,與在給計(jì)算機(jī)喂指令上起的作用同樣重要。

ES6擁有幾種重要的特性可以顯著改善這些模式,包括:迭代器,generator,模塊,和類。

迭代器

迭代器(iterator) 是一種結(jié)構(gòu)化的模式,用于從一個(gè)信息源中以一次一個(gè)的方式抽取信息。這種模式在程序設(shè)計(jì)中存在很久了。而且不可否認(rèn)的是,不知從什么時(shí)候起JS開發(fā)者們就已經(jīng)特別地設(shè)計(jì)并實(shí)現(xiàn)了迭代器,所以它根本不是什么新的話題。

ES6所做的是,為迭代器引入了一個(gè)隱含的標(biāo)準(zhǔn)化接口。許多在JavaScript中內(nèi)建的數(shù)據(jù)結(jié)構(gòu)現(xiàn)在都會(huì)暴露一個(gè)實(shí)現(xiàn)了這個(gè)標(biāo)準(zhǔn)的迭代器。而且你也可以構(gòu)建自己的遵循同樣標(biāo)準(zhǔn)的迭代器,來使互用性最大化。

迭代器是一種消費(fèi)數(shù)據(jù)的方法,它是組織有順序的,相繼的,基于抽取的。

舉個(gè)例子,你可能實(shí)現(xiàn)一個(gè)工具,它在每次被請求時(shí)產(chǎn)生一個(gè)新的唯一的標(biāo)識(shí)符。或者你可能循環(huán)一個(gè)固定的列表以輪流的方式產(chǎn)生一系列無限多的值。或者你可以在一個(gè)數(shù)據(jù)庫查詢的結(jié)果上添加一個(gè)迭代器來一次抽取一行結(jié)果。

雖然在JS中它們不經(jīng)常以這樣的方式被使用,但是迭代器還可以認(rèn)為是每次控制行為中的一個(gè)步驟。這會(huì)在考慮generator時(shí)得到相當(dāng)清楚的展示(參見本章稍后的“Generator”),雖然你當(dāng)然可以不使用generator而做同樣的事。

接口

在本書寫作的時(shí)候,ES6的25.1.1.2部分 (https://people.mozilla.org/~jorendorff/es6-draft.html#sec-iterator-interface) 詳述了Iterator接口,它有如下的要求:

Iterator [必須]
    next() {method}: 取得下一個(gè)IteratorResult

有兩個(gè)可選成員,有些迭代器用它們進(jìn)行了擴(kuò)展:

Iterator [可選]
    return() {method}: 停止迭代并返回IteratorResult
    throw() {method}: 通知錯(cuò)誤并返回IteratorResult

接口IteratorResult被規(guī)定為:

IteratorResult
    value {property}: 當(dāng)前的迭代值或最終的返回值
        (如果它的值為`undefined`,是可選的)
    done {property}: 布爾值,指示完成的狀態(tài)

注意: 我稱這些接口是隱含的,不是因?yàn)樗鼈儧]有在語言規(guī)范中被明確地被說出來 —— 它們被說出來了!—— 而是因?yàn)樗鼈儧]有作為可以直接訪問的對(duì)象暴露給代碼。在ES6中,JavaScript不支持任何“接口”的概念,所以在你自己的代碼中遵循它們純粹是慣例上的。但是,不論JS在何處需要一個(gè)迭代器 —— 例如在一個(gè)for..of循環(huán)中 —— 你提供的東西必須遵循這些接口,否則代碼就會(huì)失敗。

還有一個(gè)Iterable接口,它描述了一定能夠產(chǎn)生迭代器的對(duì)象:

Iterable
    @@iterator() {method}: 產(chǎn)生一個(gè)迭代器

如果你回憶一下第二章的“內(nèi)建Symbol”,@@iterator是一種特殊的內(nèi)建symbol,表示可以為對(duì)象產(chǎn)生迭代器的方法。

IteratorResult

IteratorResult接口規(guī)定從任何迭代器操作的返回值都是這樣形式的對(duì)象:

{ value: .. , done: true / false }

內(nèi)建迭代器將總是返回這種形式的值,當(dāng)然,更多的屬性也允許出現(xiàn)在這個(gè)返回值中,如果有必要的話。

例如,一個(gè)自定義的迭代器可能會(huì)在結(jié)果對(duì)象中加入額外的元數(shù)據(jù)(比如,數(shù)據(jù)是從哪里來的,取得它花了多久,緩存過期的時(shí)間長度,下次請求的恰當(dāng)頻率,等等)。

注意: 從技術(shù)上講,在值為undefined的情況下,value是可選的,它將會(huì)被認(rèn)為是不存在或者是沒有被設(shè)置。因?yàn)椴还芩潜硎镜木褪沁@個(gè)值還是完全不存在,訪問res.value都將會(huì)產(chǎn)生undefined,所以這個(gè)屬性的存在/不存在更大程度上是一個(gè)實(shí)現(xiàn)或者優(yōu)化(或兩者)的細(xì)節(jié),而非一個(gè)功能上的問題。

next()迭代

讓我們來看一個(gè)數(shù)組,它是一個(gè)可迭代對(duì)象,可以生成一個(gè)迭代器來消費(fèi)它的值:

var arr = [1,2,3];

var it = arr[Symbol.iterator]();

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

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

每一次定位在Symbol.iterator上的方法在值arr上被調(diào)用時(shí),它都將生成一個(gè)全新的迭代器。大多數(shù)的數(shù)據(jù)結(jié)構(gòu)都會(huì)這么做,包括所有內(nèi)建在JS中的數(shù)據(jù)結(jié)構(gòu)。

然而,像事件隊(duì)列這樣的結(jié)構(gòu)也許只能生成一個(gè)單獨(dú)的迭代器(單例模式)。或者某種結(jié)構(gòu)可能在同一時(shí)間內(nèi)只允許存在一個(gè)唯一的迭代器,要求當(dāng)前的迭代器必須完成,才能創(chuàng)建一個(gè)新的。

前一個(gè)代碼段中的it迭代器不會(huì)再你得到值3時(shí)報(bào)告done: true。你必須再次調(diào)用next(),實(shí)質(zhì)上越過數(shù)組末尾的值,才能得到完成信號(hào)done: true。在這一節(jié)稍后會(huì)清楚地講解這種設(shè)計(jì)方式的原因,但是它通常被認(rèn)為是一種最佳實(shí)踐。

基本類型的字符串值也默認(rèn)地是可迭代對(duì)象:

var greeting = "hello world";

var it = greeting[Symbol.iterator]();

it.next();      // { value: "h", done: false }
it.next();      // { value: "e", done: false }
..

注意: 從技術(shù)上講,這個(gè)基本類型值本身不是可迭代對(duì)象,但多虧了“封箱”,"hello world"被強(qiáng)制轉(zhuǎn)換為它的String對(duì)象包裝形式, 才是一個(gè)可迭代對(duì)象。更多信息參見本系列的 類型與文法

ES6還包括幾種新的數(shù)據(jù)結(jié)構(gòu),稱為集合(參見第五章)。這些集合不僅本身就是可迭代對(duì)象,而且它們還提供API方法來生成一個(gè)迭代器,例如:

var m = new Map();
m.set( "foo", 42 );
m.set( { cool: true }, "hello world" );

var it1 = m[Symbol.iterator]();
var it2 = m.entries();

it1.next();     // { value: [ "foo", 42 ], done: false }
it2.next();     // { value: [ "foo", 42 ], done: false }
..

一個(gè)迭代器的next(..)方法能夠可選地接受一個(gè)或多個(gè)參數(shù)。大多數(shù)內(nèi)建的迭代器不會(huì)實(shí)施這種能力,雖然一個(gè)generator的迭代器絕對(duì)會(huì)這么做(參見本章稍后的“Generator”)。

根據(jù)一般的慣例,包括所有的內(nèi)建迭代器,在一個(gè)已經(jīng)被耗盡的迭代器上調(diào)用next(..)不是一個(gè)錯(cuò)誤,而是簡單地持續(xù)返回結(jié)果{ value: undefined, done: true }

可選的return(..)throw(..)

在迭代器接口上的可選方法 —— return(..)throw(..) —— 在大多數(shù)內(nèi)建的迭代器上都沒有被實(shí)現(xiàn)。但是,它們在generator的上下文環(huán)境中絕對(duì)有某些含義,所以更具體的信息可以參看“Generator”。

return(..)被定義為向一個(gè)迭代器發(fā)送一個(gè)信號(hào),告知它消費(fèi)者代碼已經(jīng)完成而且不會(huì)再從它那里抽取更多的值。這個(gè)信號(hào)可以用于通知生產(chǎn)者(應(yīng)答next(..)調(diào)用的迭代器)去實(shí)施一些可能的清理作業(yè),比如釋放/關(guān)閉網(wǎng)絡(luò),數(shù)據(jù)庫,或者文件引用資源。

如果一個(gè)迭代器擁有return(..),而且發(fā)生了可以自動(dòng)被解釋為非正常或者提前終止消費(fèi)迭代器的任何情況,return(..)就將會(huì)被自動(dòng)調(diào)用。你也可以手動(dòng)調(diào)用return(..)

return(..)將會(huì)像next(..)一樣返回一個(gè)IteratorResult對(duì)象。一般來說,你向return(..)發(fā)送的可選值將會(huì)在這個(gè)IteratorResult中作為value發(fā)送回來,雖然在一些微妙的情況下這可能不成立。

throw(..)被用于向一個(gè)迭代器發(fā)送一個(gè)異常/錯(cuò)誤信號(hào),與return(..)隱含的完成信號(hào)相比,它可能會(huì)被迭代器用于不同的目的。它不一定像return(..)一樣暗示著迭代器的完全停止。

例如,在generator迭代器中,throw(..)實(shí)際上會(huì)將一個(gè)被拋出的異常注射到generator暫停的執(zhí)行環(huán)境中,這個(gè)異常可以用try..catch捕獲。一個(gè)未捕獲的throw(..)異常將會(huì)導(dǎo)致generator的迭代器異常中止。

注意: 根據(jù)一般的慣例,在return(..)throw(..)被調(diào)用之后,一個(gè)迭代器就不應(yīng)該在產(chǎn)生任何結(jié)果了。

迭代器循環(huán)

正如我們在第二章的“for..of”一節(jié)中講解的,ES6的for..of循環(huán)可以直接消費(fèi)一個(gè)規(guī)范的可迭代對(duì)象。

如果一個(gè)迭代器也是一個(gè)可迭代對(duì)象,那么它就可以直接與for..of循環(huán)一起使用。通過給予迭代器一個(gè)簡單地返回它自身的Symbol.iterator方法,你就可以使它成為一個(gè)可迭代對(duì)象:

var it = {
    // 使迭代器`it`成為一個(gè)可迭代對(duì)象
    [Symbol.iterator]() { return this; },

    next() { .. },
    ..
};

it[Symbol.iterator]() === it;       // true

現(xiàn)在我們就可以用一個(gè)for..of循環(huán)來消費(fèi)迭代器it了:

for (var v of it) {
    console.log( v );
}

為了完全理解這樣的循環(huán)如何工作,回憶下第二章中的for..of循環(huán)的for等價(jià)物:

for (var v, res; (res = it.next()) && !res.done; ) {
    v = res.value;
    console.log( v );
}

如果你仔細(xì)觀察,你會(huì)發(fā)現(xiàn)it.next()是在每次迭代之前被調(diào)用的,然后res.done才被查詢。如果res.donetrue,那么這個(gè)表達(dá)式將會(huì)求值為false于是這次迭代不會(huì)發(fā)生。

回憶一下之前我們建議說,迭代器一般不應(yīng)與最終預(yù)期的值一起返回done: true。現(xiàn)在你知道為什么了。

如果一個(gè)迭代器返回了{ done: true, value: 42 }for..of循環(huán)將完全扔掉值42。因此,假定你的迭代器可能會(huì)被for..of循環(huán)或它的for等價(jià)物這樣的模式消費(fèi)的話,你可能應(yīng)當(dāng)?shù)鹊侥阋呀?jīng)返回了所有相關(guān)的迭代值之后才返回done: true來表示完成。

警告: 當(dāng)然,你可以有意地將你的迭代器設(shè)計(jì)為將某些相關(guān)的valuedone: true同時(shí)返回。但除非你將此情況在文檔中記錄下來,否則不要這么做,因?yàn)檫@樣會(huì)隱含地強(qiáng)制你的迭代器消費(fèi)者使用一種,與我們剛才描述的for..of或它的手動(dòng)等價(jià)物不同的模式來進(jìn)行迭代。

自定義迭代器

除了標(biāo)準(zhǔn)的內(nèi)建迭代器,你還可以制造你自己的迭代器!所有使它們可以與ES6消費(fèi)設(shè)施(例如,for..of循環(huán)和...操作符)進(jìn)行互動(dòng)的代價(jià)就是遵循恰當(dāng)?shù)慕涌凇?/p>

讓我們試著構(gòu)建一個(gè)迭代器,它能夠以斐波那契(Fibonacci)數(shù)列的形式產(chǎn)生無限多的數(shù)字序列:

var Fib = {
    [Symbol.iterator]() {
        var n1 = 1, n2 = 1;

        return {
            // 使迭代器成為一個(gè)可迭代對(duì)象
            [Symbol.iterator]() { return this; },

            next() {
                var current = n2;
                n2 = n1;
                n1 = n1 + current;
                return { value: current, done: false };
            },

            return(v) {
                console.log(
                    "Fibonacci sequence abandoned."
                );
                return { value: v, done: true };
            }
        };
    }
};

for (var v of Fib) {
    console.log( v );

    if (v > 50) break;
}
// 1 1 2 3 5 8 13 21 34 55
// Fibonacci sequence abandoned.

警告: 如果我們沒有插入break條件,這個(gè)for..of循環(huán)將會(huì)永遠(yuǎn)運(yùn)行下去,這回破壞你的程序,因此可能不是我們想要的!

方法Fib[Symbol.iterator]()在被調(diào)用時(shí)返回帶有next()return(..)方法的迭代器對(duì)象。它的狀態(tài)通過變量n1n2維護(hù)在閉包中。

接下來讓我們考慮一個(gè)迭代器,它被設(shè)計(jì)為執(zhí)行一系列(也叫隊(duì)列)動(dòng)作,一次一個(gè):

var tasks = {
    [Symbol.iterator]() {
        var steps = this.actions.slice();

        return {
            // 使迭代器成為一個(gè)可迭代對(duì)象
            [Symbol.iterator]() { return this; },

            next(...args) {
                if (steps.length > 0) {
                    let res = steps.shift()( ...args );
                    return { value: res, done: false };
                }
                else {
                    return { done: true }
                }
            },

            return(v) {
                steps.length = 0;
                return { value: v, done: true };
            }
        };
    },
    actions: []
};

tasks上的迭代器步過在數(shù)組屬性actions中找到的函數(shù),并每次執(zhí)行它們中的一個(gè),并傳入你傳遞給next(..)的任何參數(shù)值,并在標(biāo)準(zhǔn)的IteratorResult對(duì)象中向你返回任何它返回的東西。

這是我們?nèi)绾问褂眠@個(gè)tasks隊(duì)列:

tasks.actions.push(
    function step1(x){
        console.log( "step 1:", x );
        return x * 2;
    },
    function step2(x,y){
        console.log( "step 2:", x, y );
        return x + (y * 2);
    },
    function step3(x,y,z){
        console.log( "step 3:", x, y, z );
        return (x * y) + z;
    }
);

var it = tasks[Symbol.iterator]();

it.next( 10 );          // step 1: 10
                        // { value:   20, done: false }

it.next( 20, 50 );      // step 2: 20 50
                        // { value:  120, done: false }

it.next( 20, 50, 120 ); // step 3: 20 50 120
                        // { value: 1120, done: false }

it.next();              // { done: true }

這種特別的用法證實(shí)了迭代器可以是一種具有組織功能的模式,不僅僅是數(shù)據(jù)。這也聯(lián)系著我們在下一節(jié)關(guān)于generator將要看到的東西。

你甚至可以更有創(chuàng)意一些,在一塊數(shù)據(jù)上定義一個(gè)表示元操作的迭代器。例如,我們可以為默認(rèn)從0開始遞增至(或遞減至,對(duì)于負(fù)數(shù)來說)指定數(shù)字的一組數(shù)字定義一個(gè)迭代器。

考慮如下代碼:

if (!Number.prototype[Symbol.iterator]) {
    Object.defineProperty(
        Number.prototype,
        Symbol.iterator,
        {
            writable: true,
            configurable: true,
            enumerable: false,
            value: function iterator(){
                var i, inc, done = false, top = +this;

                // 正向迭代還是負(fù)向迭代?
                inc = 1 * (top < 0 ? -1 : 1);

                return {
                    // 使迭代器本身成為一個(gè)可迭代對(duì)象!
                    [Symbol.iterator](){ return this; },

                    next() {
                        if (!done) {
                            // 最初的迭代總是0
                            if (i == null) {
                                i = 0;
                            }
                            // 正向迭代
                            else if (top >= 0) {
                                i = Math.min(top,i + inc);
                            }
                            // 負(fù)向迭代
                            else {
                                i = Math.max(top,i + inc);
                            }

                            // 這次迭代之后就完了?
                            if (i == top) done = true;

                            return { value: i, done: false };
                        }
                        else {
                            return { done: true };
                        }
                    }
                };
            }
        }
    );
}

現(xiàn)在,這種創(chuàng)意給了我們什么技巧?

for (var i of 3) {
    console.log( i );
}
// 0 1 2 3

[...-3];                // [0,-1,-2,-3]

這是一些有趣的技巧,雖然其實(shí)際用途有些值得商榷。但是再一次,有人可能想知道為什么ES6沒有提供如此微小但討喜的特性呢?

如果我連這樣的提醒都沒給過你,那就是我的疏忽:像我在前面的代碼段中做的那樣擴(kuò)展原生原型,是一件你需要小心并了解潛在的危害后才應(yīng)該做的事情。

在這樣的情況下,你與其他代碼或者未來的JS特性發(fā)生沖突的可能性非常低。但是要小心微小的可能性。并在文檔中為后人詳細(xì)記錄下你在做什么。

注意: 如果你想知道更多細(xì)節(jié),我在這篇文章(http://blog.getify.com/iterating-es6-numbers/) 中詳細(xì)論述了這種特別的技術(shù)。而且這段評(píng)論(http://blog.getify.com/iterating-es6-numbers/comment-page-1/#comment-535294)甚至為制造一個(gè)字符串字符范圍提出了一個(gè)相似的技巧。

消費(fèi)迭代器

我們已經(jīng)看到了使用for..of循環(huán)來一個(gè)元素一個(gè)元素地消費(fèi)一個(gè)迭代器。但是還有一些其他的ES6結(jié)構(gòu)可以消費(fèi)迭代器。

讓我們考慮一下附著這個(gè)數(shù)組上的迭代器(雖然任何我們選擇的迭代器都將擁有如下的行為):

var a = [1,2,3,4,5];

擴(kuò)散操作符...將完全耗盡一個(gè)迭代器。考慮如下代碼:

function foo(x,y,z,w,p) {
    console.log( x + y + z + w + p );
}

foo( ...a );            // 15

...還可以在一個(gè)數(shù)組內(nèi)部擴(kuò)散一個(gè)迭代器:

var b = [ 0, ...a, 6 ];
b;                      // [0,1,2,3,4,5,6]

數(shù)組解構(gòu)(參見第二章的“解構(gòu)”)可以部分地或者完全地(如果與一個(gè)...剩余/收集操作符一起使用)消費(fèi)一個(gè)迭代器:

var it = a[Symbol.iterator]();

var [x,y] = it;         // 僅從`it`中取前兩個(gè)元素
var [z, ...w] = it;     // 取第三個(gè),然后一次取得剩下所有的

// `it`被完全耗盡了嗎?是的
it.next();              // { value: undefined, done: true }

x;                      // 1
y;                      // 2
z;                      // 3
w;                      // [4,5]

Generator

所有的函數(shù)都會(huì)運(yùn)行至完成,對(duì)吧?換句話說,一旦一個(gè)函數(shù)開始運(yùn)行,在它完成之前沒有任何東西能夠打斷它。

至少對(duì)于到目前為止的JavaScript的整個(gè)歷史來說是這樣的。在ES6中,引入了一個(gè)有些異乎尋常的新形式的函數(shù),稱為generator。一個(gè)generator可以在運(yùn)行期間暫停它自己,還可以立即或者稍后繼續(xù)運(yùn)行。所以顯然它沒有普通函數(shù)那樣的運(yùn)行至完成的保證。

另外,在運(yùn)行期間的每次暫停/繼續(xù)輪回都是一個(gè)雙向消息傳遞的好機(jī)會(huì),generator可以在這里返回一個(gè)值,而使它繼續(xù)的控制端代碼可以發(fā)回一個(gè)值。

就像前一節(jié)中的迭代器一樣,有種方式可以考慮generator是什么,或者說它對(duì)什么最有用。對(duì)此沒有一個(gè)正確的答案,但我們將試著從幾個(gè)角度考慮。

注意: 關(guān)于generator的更多信息參見本系列的 異步與性能,還可以參見本書的第四章。

語法

generator函數(shù)使用這種新語法聲明:

function *foo() {
    // ..
}

*的位置在功能上無關(guān)緊要。同樣的聲明還可以寫做以下的任意一種:

function *foo()  { .. }
function* foo()  { .. }
function * foo() { .. }
function*foo()   { .. }
..

這里 唯一 的區(qū)別就是風(fēng)格的偏好。大多數(shù)其他的文獻(xiàn)似乎喜歡function* foo(..) { .. }。我喜歡function *foo(..) { .. },所以這就是我將在本書剩余部分中表示它們的方法。

我這樣做的理由實(shí)質(zhì)上純粹是為了教學(xué)。在這本書中,當(dāng)我引用一個(gè)generator函數(shù)時(shí),我將使用*foo(..),與普通函數(shù)的foo(..)相對(duì)。我發(fā)現(xiàn)*foo(..)function *foo(..) { .. }*的位置更加吻合。

另外,就像我們在第二章的簡約方法中看到的,在對(duì)象字面量中有一種簡約generator形式:

var a = {
    *foo() { .. }
};

我要說在簡約generator中,*foo() { .. }要比* foo() { .. }更自然。這進(jìn)一步表明了為何使用*foo()匹配一致性。

一致性使理解與學(xué)習(xí)更輕松。

執(zhí)行一個(gè)Generator

雖然一個(gè)generator使用*進(jìn)行聲明,但是你依然可以像一個(gè)普通函數(shù)那樣執(zhí)行它:

foo();

你依然可以傳給它參數(shù)值,就像:

function *foo(x,y) {
    // ..
}

foo( 5, 10 );

主要區(qū)別在于,執(zhí)行一個(gè)generator,比如foo(5,10),并不實(shí)際運(yùn)行g(shù)enerator中的代碼。取而代之的是,它生成一個(gè)迭代器來控制generator執(zhí)行它的代碼。

我們將在稍后的“迭代器控制”中回到這個(gè)話題,但是簡要地說:

function *foo() {
    // ..
}

var it = foo();

// 要開始/推進(jìn)`*foo()`,調(diào)用
// `it.next(..)`

yield

Generator還有一個(gè)你可以在它們內(nèi)部使用的新關(guān)鍵字,用來表示暫停點(diǎn):yield。考慮如下代碼:

function *foo() {
    var x = 10;
    var y = 20;

    yield;

    var z = x + y;
}

在這個(gè)*foo()generator中,前兩行的操作將會(huì)在開始時(shí)運(yùn)行,然后yield將會(huì)暫停這個(gè)generator。如果這個(gè)generator被繼續(xù),*foo()的最后一行將運(yùn)行。在一個(gè)generator中yield可以出現(xiàn)任意多次(或者,在技術(shù)上講,根本不出現(xiàn)!)。

你甚至可以在一個(gè)循環(huán)內(nèi)部放置yield,它可以表示一個(gè)重復(fù)的暫停點(diǎn)。事實(shí)上,一個(gè)永不完成的循環(huán)就意味著一個(gè)永不完成的generator,這是完全合法的,而且有時(shí)候完全是你需要的。

yield不只是一個(gè)暫停點(diǎn)。它是在暫停generator時(shí)發(fā)送出一個(gè)值的表達(dá)式。這里是一個(gè)位于generator中的while..true循環(huán),它每次迭代時(shí)yield出一個(gè)新的隨機(jī)數(shù):

function *foo() {
    while (true) {
        yield Math.random();
    }
}

yield ..表達(dá)式不僅發(fā)送一個(gè)值 —— 不帶值的yieldyield undefined相同 —— 它還接收(也就是,被替換為)最終的繼續(xù)值。考慮如下代碼:

function *foo() {
    var x = yield 10;
    console.log( x );
}

這個(gè)generator在暫停它自己時(shí)將首先yield出值10。當(dāng)你繼續(xù)這個(gè)generator時(shí) —— 使用我們先前提到的it.next(..) —— 無論你使用什么值繼續(xù)它,這個(gè)值都將替換/完成整個(gè)表達(dá)式yield 10,這意味著這個(gè)值將被賦值給變量x

一個(gè)yield..表達(dá)式可以出現(xiàn)在任意普通表達(dá)式可能出現(xiàn)的地方。例如:

function *foo() {
    var arr = [ yield 1, yield 2, yield 3 ];
    console.log( arr, yield 4 );
}

這里的*foo()有四個(gè)yield ..表達(dá)式。其中每個(gè)yield都會(huì)導(dǎo)致generator暫停以等待一個(gè)繼續(xù)值,這個(gè)繼續(xù)值稍后被用于各個(gè)表達(dá)式環(huán)境中。

yield在技術(shù)上講不是一個(gè)操作符,雖然像yield 1這樣使用時(shí)看起來確實(shí)很像。因?yàn)?code>yield可以像var x = yield這樣完全通過自己被使用,所以將它認(rèn)為是一個(gè)操作符有時(shí)令人困惑。

從技術(shù)上講,yield ..a = 3這樣的賦值表達(dá)式擁有相同的“表達(dá)式優(yōu)先級(jí)” —— 概念上和操作符優(yōu)先級(jí)很相似。這意味著yield ..基本上可以出現(xiàn)在任何a = 3可以合法出現(xiàn)的地方。

讓我們展示一下這種對(duì)稱性:

var a, b;

a = 3;                  // 合法
b = 2 + a = 3;          // 不合法
b = 2 + (a = 3);        // 合法

yield 3;                // 合法
a = 2 + yield 3;        // 不合法
a = 2 + (yield 3);      // 合法

注意: 如果你好好考慮一下,認(rèn)為一個(gè)yield ..表達(dá)式與一個(gè)賦值表達(dá)式的行為相似在概念上有些道理。當(dāng)一個(gè)被暫停的generator被繼續(xù)時(shí),它就以一種與被這個(gè)繼續(xù)值“賦值”區(qū)別不大的方式,被這個(gè)值完成/替換。

要點(diǎn):如果你需要yield ..出現(xiàn)在a = 3這樣的賦值本不被允許出現(xiàn)的位置,那么它就需要被包在一個(gè)( )中。

因?yàn)?code>yield關(guān)鍵字的優(yōu)先級(jí)很低,幾乎任何出現(xiàn)在yield ..之后的表達(dá)式都會(huì)在被yield發(fā)送之前首先被計(jì)算。只有擴(kuò)散操作符...和逗號(hào)操作符,擁有更低的優(yōu)先級(jí),這意味著他們會(huì)在yield已經(jīng)被求值之后才會(huì)被處理。

所以正如帶有多個(gè)操作符的普通語句一樣,存在另一個(gè)可能需要( )來覆蓋(提升)yield的低優(yōu)先級(jí)的情況,就像這些表達(dá)式之間的區(qū)別:

yield 2 + 3;            // 與`yield (2 + 3)`相同

(yield 2) + 3;          // 首先`yield 2`,然后`+ 3`

=賦值一樣,yield也是“右結(jié)合性”的,這意味著多個(gè)接連出現(xiàn)的yield表達(dá)式被視為從右到左被( .. )分組。所以,yield yield yield 3將被視為yield (yield (yield 3))。像((yield) yield) yield 3這樣的“左結(jié)合性”解釋沒有意義。

和其他操作符一樣,yield與其他操作符或yield組合時(shí)為了使你的意圖沒有歧義,使用( .. )分組是一個(gè)好主意,即使這不是嚴(yán)格要求的。

注意: 更多關(guān)于操作符優(yōu)先級(jí)和結(jié)合性的信息,參見本系列的 類型與文法

yield *

*使一個(gè)function聲明成為一個(gè)function *generator聲明的方式一樣,一個(gè)*使yield成為一個(gè)機(jī)制非常不同的yield *,稱為 yield委托。從文法上講,yield *..的行為與yield ..相同,就像在前一節(jié)討論過的那樣。

yield * ..需要一個(gè)可迭代對(duì)象;然后它調(diào)用這個(gè)可迭代對(duì)象的迭代器,并將它自己的宿主generator的控制權(quán)委托給那個(gè)迭代器,直到它被耗盡。考慮如下代碼:

function *foo() {
    yield *[1,2,3];
}

注意: 與generator聲明中*的位置(早先討論過)一樣,在yield *表達(dá)式中的*的位置在風(fēng)格上由你來決定。大多數(shù)其他文獻(xiàn)偏好yield* ..,但是我喜歡yield *..,理由和我們已經(jīng)討論過的相同。

[1,2,3]產(chǎn)生一個(gè)將會(huì)步過它的值的迭代器,所以generator*foo()將會(huì)在被消費(fèi)時(shí)產(chǎn)生這些值。另一種說明這種行為的方式是,yield委托到了另一個(gè)generator:

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

function *bar() {
    yield *foo();
}

當(dāng)*bar()調(diào)用*foo()產(chǎn)生的迭代器通過yield *受到委托,意味著無論*foo()產(chǎn)生什么值都會(huì)被*bar()產(chǎn)生。

yield ..中表達(dá)式的完成值來自于使用it.next(..)繼續(xù)generator,而yield *..表達(dá)式的完成值來自于受到委托的迭代器的返回值(如果有的話)。

內(nèi)建的迭代器一般沒有返回值,正如我們在本章早先的“迭代器循環(huán)”一節(jié)的末尾講過的。但是如果你定義你自己的迭代器(或者generator),你就可以將它設(shè)計(jì)為return一個(gè)值,yield *..將會(huì)捕獲它:

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

function *bar() {
    var x = yield *foo();
    console.log( "x:", x );
}

for (var v of bar()) {
    console.log( v );
}
// 1 2 3
// x: { value: 4, done: true }

雖然值12,和3*foo()中被yield出來,然后從*bar()中被yield出來,但是從*foo()中返回的值4是表達(dá)式yield *foo()的完成值,然后它被賦值給x

因?yàn)?code>yield *可以調(diào)用另一個(gè)generator(通過委托到它的迭代器的方式),它還可以通過調(diào)用自己來實(shí)施某種generator遞歸:

function *foo(x) {
    if (x < 3) {
        x = yield *foo( x + 1 );
    }
    return x * 2;
}

foo( 1 );

取得foo(1)的結(jié)果并調(diào)用迭代器的next()來使它運(yùn)行它的遞歸步驟,結(jié)果將是24。第一次*foo()運(yùn)行時(shí)x擁有值1,它是x < 3x + 1被遞歸地傳遞到*foo(..),所以之后的x2。再一次遞歸調(diào)用導(dǎo)致x3

現(xiàn)在,因?yàn)?code>x < 3失敗了,遞歸停止,而且return 3 * 26給回前一個(gè)調(diào)用的yeild *..表達(dá)式,它被賦值給x。另一個(gè)return 6 * 2返回12給前一個(gè)調(diào)用的x。最終12 * 2,即24,從generator*foo(..)運(yùn)行的完成中被返回。

迭代器控制

早先,我們簡要地介紹了generator是由迭代器控制的概念。現(xiàn)在讓我們完整地深入這個(gè)話題。

回憶一下前一節(jié)的遞歸*for(..)。這是我們?nèi)绾芜\(yùn)行它:

function *foo(x) {
    if (x < 3) {
        x = yield *foo( x + 1 );
    }
    return x * 2;
}

var it = foo( 1 );
it.next();              // { value: 24, done: true }

在這種情況下,generator并沒有真正暫停過,因?yàn)檫@里沒有yield ..表達(dá)式。而yield *只是通過遞歸調(diào)用保持當(dāng)前的迭代步驟繼續(xù)運(yùn)行下去。所以,僅僅對(duì)迭代器的next()函數(shù)進(jìn)行一次調(diào)用就完全地運(yùn)行了generator。

現(xiàn)在讓我們考慮一個(gè)有多個(gè)步驟并且因此有多個(gè)產(chǎn)生值的generator:

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

我們已經(jīng)知道我們可以是使用一個(gè)for..of循環(huán)來消費(fèi)一個(gè)迭代器,即便它是一個(gè)附著在*foo()這樣的generator上:

for (var v of foo()) {
    console.log( v );
}
// 1 2 3

注意: for..of循環(huán)需要一個(gè)可迭代對(duì)象。一個(gè)generator函數(shù)引用(比如foo)本身不是一個(gè)可迭代對(duì)象;你必須使用foo()來執(zhí)行它以得到迭代器(它也是一個(gè)可迭代對(duì)象,正如我們在本章早先講解過的)。理論上你可以使用一個(gè)實(shí)質(zhì)上僅僅執(zhí)行return this()Symbol.iterator函數(shù)來擴(kuò)展GeneratorPrototype(所有g(shù)enerator函數(shù)的原型)。這將使foo引用本身成為一個(gè)可迭代對(duì)象,也就意味著for (var v of foo) { .. }(注意在foo上沒有())將可以工作。

讓我們手動(dòng)迭代這個(gè)generator:

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

var it = foo();

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

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

如果你仔細(xì)觀察,這里有三個(gè)yield語句和四個(gè)next()調(diào)用。這可能看起來像是一個(gè)奇怪的不匹配。事實(shí)上,假定所有的東西都被求值并且generator完全運(yùn)行至完成的話,next()調(diào)用將總是比yield表達(dá)式多一個(gè)。

但是如果你相反的角度觀察(從里向外而不是從外向里),yieldnext()之間的匹配就顯得更有道理。

回憶一下,yield ..表達(dá)式將被你用于繼續(xù)generator的值完成。這意味著你傳遞給next(..)的參數(shù)值將完成任何當(dāng)前暫停中等待完成的yield ..表達(dá)式。

讓我們這樣展示一下這種視角:

function *foo() {
    var x = yield 1;
    var y = yield 2;
    var z = yield 3;
    console.log( x, y, z );
}

在這個(gè)代碼段中,每個(gè)yield ..都送出一個(gè)值(123),但更直接的是,它暫停了generator來等待一個(gè)值。換句話說,它就像在問這樣一個(gè)問題,“我應(yīng)當(dāng)在這里用什么值?我會(huì)在這里等你告訴我。”

現(xiàn)在,這是我們?nèi)绾慰刂?code>*foo()來啟動(dòng)它:

var it = foo();

it.next();              // { value: 1, done: false }

這第一個(gè)next()調(diào)用從generator初始的暫停狀態(tài)啟動(dòng)了它,并運(yùn)行至第一個(gè)yield。在你調(diào)用第一個(gè)next()的那一刻,并沒有yield ..表達(dá)式等待完成。如果你給第一個(gè)next()調(diào)用傳遞一個(gè)值,目前它會(huì)被扔掉,因?yàn)闆]有yield等著接受這樣的一個(gè)值。

注意: 一個(gè)“ES6之后”時(shí)間表中的早期提案 允許你在generator內(nèi)部通過一個(gè)分離的元屬性(見第七章)來訪問一個(gè)被傳入初始next(..)調(diào)用的值。

現(xiàn)在,讓我們回答那個(gè)未解的問題,“我應(yīng)當(dāng)給x賦什么值?” 我們將通過給 下一個(gè) next(..)調(diào)用發(fā)送一個(gè)值來回答:

it.next( "foo" );       // { value: 2, done: false }

現(xiàn)在,x將擁有值"foo",但我們也問了一個(gè)新的問題,“我應(yīng)當(dāng)給y賦什么值?”

it.next( "bar" );       // { value: 3, done: false }

答案給出了,另一個(gè)問題被提出了。最終答案:

it.next( "baz" );       // "foo" "bar" "baz"
                        // { value: undefined, done: true }

現(xiàn)在,每一個(gè)yield ..的“問題”是如何被 下一個(gè) next(..)調(diào)用回答的,所以我們觀察到的那個(gè)“額外的”next()調(diào)用總是使一切開始的那一個(gè)。

讓我們把這些步驟放在一起:

var it = foo();

// 啟動(dòng)generator
it.next();              // { value: 1, done: false }

// 回答第一個(gè)問題
it.next( "foo" );       // { value: 2, done: false }

// 回答第二個(gè)問題
it.next( "bar" );       // { value: 3, done: false }

// 回答第三個(gè)問題
it.next( "baz" );       // "foo" "bar" "baz"
                        // { value: undefined, done: true }

在生成器的每次迭代都簡單地為消費(fèi)者生成一個(gè)值的情況下,你可認(rèn)為一個(gè)generator是一個(gè)值的生成器。

但是在更一般的意義上,也許將generator認(rèn)為是一個(gè)受控制的,累進(jìn)的代碼執(zhí)行過程更恰當(dāng),與早先“自定義迭代器”一節(jié)中的tasks隊(duì)列的例子非常相像。

注意: 這種視角正是我們將如何在第四章中重溫generator的動(dòng)力。特別是,next(..)沒有理由一定要在前一個(gè)next(..)完成之后立即被調(diào)用。雖然generator的內(nèi)部執(zhí)行環(huán)境被暫停了,程序的其他部分仍然沒有被阻塞,這包括控制generator什么時(shí)候被繼續(xù)的異步動(dòng)作能力。

提前完成

正如我們在本章早先講過的,連接到一個(gè)generator的迭代器支持可選的return(..)throw(..)方法。它們倆都有立即中止一個(gè)暫停的的generator的效果。

考慮如下代碼:

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

var it = foo();

it.next();              // { value: 1, done: false }

it.return( 42 );        // { value: 42, done: true }

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

return(x)有點(diǎn)像強(qiáng)制一個(gè)return x就在那個(gè)時(shí)刻被處理,這樣你就立即得到這個(gè)指定的值。一旦一個(gè)generator完成,無論是正常地還是像展示的那樣提前地,它就不再處理任何代碼或返回任何值了。

return(..)除了可以手動(dòng)調(diào)用,它還在迭代的最后被任何ES6中消費(fèi)迭代器的結(jié)構(gòu)自動(dòng)調(diào)用,比如for..of循環(huán)和...擴(kuò)散操作符。

這種能力的目的是,在控制端的代碼不再繼續(xù)迭代generator時(shí)它可以收到通知,這樣它就可能做一些清理工作(釋放資源,復(fù)位狀態(tài),等等)。與普通函數(shù)的清理模式完全相同,達(dá)成這個(gè)目的的主要方法是使用一個(gè)finally子句:

function *foo() {
    try {
        yield 1;
        yield 2;
        yield 3;
    }
    finally {
        console.log( "cleanup!" );
    }
}

for (var v of foo()) {
    console.log( v );
}
// 1 2 3
// cleanup!

var it = foo();

it.next();              // { value: 1, done: false }
it.return( 42 );        // cleanup!
                        // { value: 42, done: true }

警告: 不要把yield語句放在finally子句內(nèi)部!它是有效和合法的,但這確實(shí)是一個(gè)可怕的主意。它在某種意義上推遲了return(..)調(diào)用的完成,因?yàn)樵?code>finally子句中的任何yield ..表達(dá)式都被遵循來暫停和發(fā)送消息;你不會(huì)像期望的那樣立即得到一個(gè)完成的generator。基本上沒有任何好的理由去選擇這種瘋狂的 壞的部分,所以避免這么做!

前一個(gè)代碼段除了展示return(..)如何在中止generator的同時(shí)觸發(fā)finally子句,它還展示了一個(gè)generator在每次被調(diào)用時(shí)都產(chǎn)生一個(gè)全新的迭代器。事實(shí)上,你可以并發(fā)地使用連接到相同generator的多個(gè)迭代器:

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

var it1 = foo();
it1.next();             // { value: 1, done: false }
it1.next();             // { value: 2, done: false }

var it2 = foo();
it2.next();             // { value: 1, done: false }

it1.next();             // { value: 3, done: false }

it2.next();             // { value: 2, done: false }
it2.next();             // { value: 3, done: false }

it2.next();             // { value: undefined, done: true }
it1.next();             // { value: undefined, done: true }

提前中止

你可以調(diào)用throw(..)來代替return(..)調(diào)用。就像return(x)實(shí)質(zhì)上在generator當(dāng)前的暫停點(diǎn)上注入了一個(gè)return x一樣,調(diào)用throw(x)實(shí)質(zhì)上就像在暫停點(diǎn)上注入了一個(gè)throw x

除了處理異常的行為(我們在下一節(jié)講解這對(duì)try子句意味著什么),throw(..)產(chǎn)生相同的提前完成 —— 在generator當(dāng)前的暫停點(diǎn)中止它的運(yùn)行。例如:

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

var it = foo();

it.next();              // { value: 1, done: false }

try {
    it.throw( "Oops!" );
}
catch (err) {
    console.log( err ); // Exception: Oops!
}

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

因?yàn)?code>throw(..)基本上注入了一個(gè)throw ..來替換generator的yield 1這一行,而且沒有東西處理這個(gè)異常,它立即傳播回外面的調(diào)用端代碼,調(diào)用端代碼使用了一個(gè)try..catch來處理了它。

return(..)不同的是,迭代器的throw(..)方法絕不會(huì)被自動(dòng)調(diào)用。

當(dāng)然,雖然沒有在前面的代碼段中展示,但如果當(dāng)你調(diào)用throw(..)時(shí)有一個(gè)try..finally子句等在generator內(nèi)部的話,這個(gè)finally子句將會(huì)在異常被傳播回調(diào)用端代碼之前有機(jī)會(huì)運(yùn)行。

錯(cuò)誤處理

正如我們已經(jīng)得到的提示,generator中的錯(cuò)誤處理可以使用try..catch表達(dá),它在上行和下行兩個(gè)方向都可以工作。

function *foo() {
    try {
        yield 1;
    }
    catch (err) {
        console.log( err );
    }

    yield 2;

    throw "Hello!";
}

var it = foo();

it.next();              // { value: 1, done: false }

try {
    it.throw( "Hi!" );  // Hi!
                        // { value: 2, done: false }
    it.next();

    console.log( "never gets here" );
}
catch (err) {
    console.log( err ); // Hello!
}

錯(cuò)誤也可以通過yield *委托在兩個(gè)方向上傳播:

function *foo() {
    try {
        yield 1;
    }
    catch (err) {
        console.log( err );
    }

    yield 2;

    throw "foo: e2";
}

function *bar() {
    try {
        yield *foo();

        console.log( "never gets here" );
    }
    catch (err) {
        console.log( err );
    }
}

var it = bar();

try {
    it.next();          // { value: 1, done: false }

    it.throw( "e1" );   // e1
                        // { value: 2, done: false }

    it.next();          // foo: e2
                        // { value: undefined, done: true }
}
catch (err) {
    console.log( "never gets here" );
}

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

當(dāng)*foo()調(diào)用yield 1時(shí),值1原封不動(dòng)地穿過了*bar(),就像我們已經(jīng)看到過的那樣。

但這個(gè)代碼段最有趣的部分是,當(dāng)*foo()調(diào)用throw "foo: e2"時(shí),這個(gè)錯(cuò)誤傳播到了*bar()并立即被*bar()try..catch塊兒捕獲。錯(cuò)誤沒有像值1那樣穿過*bar()

然后*bar()catcherr普通地輸出("foo: e2")之后*bar()就正常結(jié)束了,這就是為什么迭代器結(jié)果{ value: undefined, done: true }it.next()中返回。

如果*bar()沒有用try..catch環(huán)繞著yield *..表達(dá)式,那么錯(cuò)誤將理所當(dāng)然地一直傳播出來,而且在它傳播的路徑上依然會(huì)完成(中止)*bar()

轉(zhuǎn)譯一個(gè)Generator

有可能在ES6之前的環(huán)境中表達(dá)generator的能力嗎?事實(shí)上是可以的,而且有好幾種了不起的工具在這么做,包括最著名的Facebook的Regenerator工具 (https://facebook.github.io/regenerator/)。

但為了更好地理解generator,讓我們試著手動(dòng)轉(zhuǎn)換一下。基本上講,我們將制造一個(gè)簡單的基于閉包的狀態(tài)機(jī)。

我們將使原本的generator非常簡單:

function *foo() {
    var x = yield 42;
    console.log( x );
}

開始之前,我們將需要一個(gè)我們能夠執(zhí)行的稱為foo()的函數(shù),它需要返回一個(gè)迭代器:

function foo() {
    // ..

    return {
        next: function(v) {
            // ..
        }

        // 我們將省略`return(..)`和`throw(..)`
    };
}

現(xiàn)在,我們需要一些內(nèi)部變量來持續(xù)跟蹤我們的“generator”的邏輯走到了哪一個(gè)步驟。我們稱它為state。我們將有三種狀態(tài):起始狀態(tài)的0,等待完成yield表達(dá)式的1,和generator完成的2

每次next(..)被調(diào)用時(shí),我們需要處理下一個(gè)步驟,然后遞增state。為了方便,我們將每個(gè)步驟放在一個(gè)switch語句的case子句中,并且我們將它放在一個(gè)next(..)可以調(diào)用的稱為nextState(..)的內(nèi)部函數(shù)中。另外,因?yàn)?code>x是一個(gè)橫跨整個(gè)“generator”作用域的變量,所以它需要存活在nextState(..)函數(shù)的外部。

這是將它們放在一起(很明顯,為了使概念的展示更清晰,它經(jīng)過了某些簡化):

function foo() {
    function nextState(v) {
        switch (state) {
            case 0:
                state++;

                // `yield`表達(dá)式
                return 42;
            case 1:
                state++;

                // `yield`表達(dá)式完成了
                x = v;
                console.log( x );

                // 隱含的`return`
                return undefined;

            // 無需處理狀態(tài)`2`
        }
    }

    var state = 0, x;

    return {
        next: function(v) {
            var ret = nextState( v );

            return { value: ret, done: (state == 2) };
        }

        // 我們將省略`return(..)`和`throw(..)`
    };
}

最后,讓我們測試一下我們的前ES6“generator”:

var it = foo();

it.next();              // { value: 42, done: false }

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

不賴吧?希望這個(gè)練習(xí)能在你的腦中鞏固這個(gè)概念:generator實(shí)際上只是狀態(tài)機(jī)邏輯的簡單語法。這使它們可以廣泛地應(yīng)用。

Generator的使用

我們現(xiàn)在非常深入地理解了generator如何工作,那么,它們在什么地方有用?

我們已經(jīng)看過了兩種主要模式:

  • 生產(chǎn)一系列值: 這種用法可以很簡單(例如,隨機(jī)字符串或者遞增的數(shù)字),或者它也可以表達(dá)更加結(jié)構(gòu)化的數(shù)據(jù)訪問(例如,迭代一個(gè)數(shù)據(jù)庫查詢結(jié)果的所有行)。

    這兩種方式中,我們使用迭代器來控制generator,這樣就可以為每次next(..)調(diào)用執(zhí)行一些邏輯。在數(shù)據(jù)解構(gòu)上的普通迭代器只不過生成值而沒有任何控制邏輯。

  • 串行執(zhí)行的任務(wù)隊(duì)列: 這種用法經(jīng)常用來表達(dá)一個(gè)算法中步驟的流程控制,其中每一步都要求從某些外部數(shù)據(jù)源取得數(shù)據(jù)。對(duì)每塊兒數(shù)據(jù)的請求可能會(huì)立即滿足,或者可能會(huì)異步延遲地滿足。

    從generator內(nèi)部代碼的角度來看,在yield的地方,同步或異步的細(xì)節(jié)是完全不透明的。另外,這些細(xì)節(jié)被有意地抽象出去,如此就不會(huì)讓這樣的實(shí)現(xiàn)細(xì)節(jié)把各個(gè)步驟間自然的,順序的表達(dá)搞得模糊不清。抽象還意味著實(shí)現(xiàn)可以被替換/重構(gòu),而根本不用碰generator中的代碼。

當(dāng)根據(jù)這些用法觀察generator時(shí),它們的含義要比僅僅是手動(dòng)狀態(tài)機(jī)的一種不同或更好的語法多多了。它們是一種用于組織和控制有序地生產(chǎn)與消費(fèi)數(shù)據(jù)的強(qiáng)大工具。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容