在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 生成器
生成器
箭頭函數(shù)
繼續(xù)迭代器
未來(lái)前景
簡(jiǎn)介
非結(jié)構(gòu)化賦值
迭代器和 for-of 循環(huán)
代理
符號(hào)
模版字符串
let 和 const
容器
模塊

生成器

我們將要討論 ES6 的最神奇的功能。

我所說(shuō)的“神奇”的意思是什么呢?對(duì)于初學(xué)者來(lái)說(shuō),該功能與 JS 中已經(jīng)存在的功能非常不同。這可能會(huì)讓初學(xué)者們?cè)诘谝淮我?jiàn)到這個(gè)功能的時(shí)候感覺(jué)晦澀難懂。從某種意義上來(lái)講,這個(gè)功能使得語(yǔ)言的內(nèi)在的很平常的行為展露在開(kāi)發(fā)者面前。如果這還不算是神奇,那我不知道什么才算了。

不僅如此:這個(gè)功能能夠簡(jiǎn)化代碼并且使得在超類上的“回調(diào)地獄”更加直白。

是不是我太看重這個(gè)了?讓我們一起深入,然后你自己就可以進(jìn)行判斷了。

介紹 ES6 生成器

什么是生成器?

我們先從下面的這里例子開(kāi)始。

function* quips(name) {
  yield "hello " + name + "!";
  yield "i hope you are enjoying the blog posts";
  if (name.startsWith("X")) {
    yield "it's cool how your name starts with X, " + name;
  }
  yield "see you later!";
}

這段代碼是一個(gè)對(duì)話貓,這可能是當(dāng)前網(wǎng)絡(luò)上最重要的一類應(yīng)用。( 繼續(xù),點(diǎn)擊鏈接,與貓一起玩耍。當(dāng)你徹底困惑了以后,你再回到這里看一下解釋。)

這個(gè)在一定程度上看起來(lái)像一個(gè)函數(shù),對(duì)不?這就被稱為生成器函數(shù),同時(shí)它與函數(shù)之間也有很多相似之處。但是你一下子就能發(fā)現(xiàn)兩個(gè)不同之處:

  • 普通的函數(shù)使用 function 作為開(kāi)始。生成器函數(shù)以 function* 開(kāi)始。
  • 在一個(gè)生成器函數(shù)中,yield 是一個(gè)關(guān)鍵字,語(yǔ)法和 return 很相似。 區(qū)別在于,一個(gè)函數(shù)( 甚至是生成器函數(shù)),只能返回一次,但是一個(gè)生成器函數(shù)能夠 yield 很多次。 yield 表達(dá)式暫停生成器的運(yùn)行,然后它能夠在之后重新被使用。

就是這樣的,以上就是普通的函數(shù)和生成器函數(shù)之間的大區(qū)別。普通的函數(shù)不能自己暫停。然而生成器函數(shù)可以自己暫停運(yùn)行。

生成器的用處

當(dāng)你調(diào)用 quips( ) 生成器函數(shù)時(shí)將會(huì)發(fā)生什么?

> var iter = quips("jorendorff");
  [object Generator]
> iter.next()
  { value: "hello jorendorff!", done: false }
> iter.next()
  { value: "i hope you are enjoying the blog posts", done: false }
> iter.next()
  { value: "see you later!", done: false }
> iter.next()
  { value: undefined, done: true }

你可能非常習(xí)慣于普通的函數(shù)以及他們的表現(xiàn)。當(dāng)你調(diào)用他們的時(shí)候,他們立即開(kāi)始運(yùn)行,當(dāng)遇到 return 或者 throw 的時(shí)候,他們停止運(yùn)行。任何一個(gè) JS 程序員都非常習(xí)慣于上述的過(guò)程。

調(diào)用一個(gè)生成器看起來(lái)是一樣的:quips("jorendorff")。 但是當(dāng)你調(diào)用一個(gè)生成器,他還不開(kāi)始運(yùn)行。反而,它返回一個(gè)暫停的生成器對(duì)象( 在上述的例子中被稱為 iter )。你可以認(rèn)為這個(gè)生成器對(duì)象是一個(gè)函數(shù)調(diào)用,暫時(shí)停止。特別的是,其在生成器函數(shù)一開(kāi)始就停止了,在運(yùn)行代碼的第一行之前。

每次你調(diào)用生成器對(duì)象的 .next( ) 方法,函數(shù)將其自己解凍并運(yùn)行直到其到達(dá)下一個(gè) yield 表達(dá)式。

這就是上面代碼中我們?yōu)槭裁匆{(diào)用 iter.next( ),調(diào)用后我們獲得一個(gè)不同的字符串值。這些值都是由 quips( ) 里的 yield 表達(dá)式產(chǎn)生的。

在最后一個(gè) iter.next( ) 調(diào)用中,我們最后結(jié)束了生成器函數(shù),所以結(jié)果的 .done 領(lǐng)域的值為true。 一個(gè)函數(shù)的結(jié)束就像是返回 undefined,而且這也是為什么結(jié)果的 .value 領(lǐng)域是不確定的 ( undefined)。

現(xiàn)在可能是一個(gè)好機(jī)會(huì)來(lái)返回到上面的對(duì)話貓的例子那頁(yè)面上,同時(shí)真正地可以玩轉(zhuǎn)代碼。嘗試著在一個(gè)循環(huán)中加入一個(gè) yield。這會(huì)發(fā)生什么呢?

在技術(shù)層面上,每一次一個(gè)生成器進(jìn)行 yield 操作,其堆棧楨,包括局部變量,參數(shù),臨時(shí)值,以及在生成器中的執(zhí)行的當(dāng)前位置,被從棧中刪除。然而,生成器對(duì)象保有這個(gè)堆棧幀的引用(或者是副本)。因此,接下來(lái)的調(diào)用 .next( ) 可以重新激活它并繼續(xù)執(zhí)行。

值得指出的是,生成器都沒(méi)有線程。在能使用線程的語(yǔ)言中,多份代碼可以在同一時(shí)間運(yùn)行,這通常導(dǎo)致了競(jìng)爭(zhēng)條件,非確定性和非常非常好的性能。生成器和這完全不同。當(dāng)生成器運(yùn)行時(shí),它與調(diào)用者運(yùn)行在同一個(gè)線程中。執(zhí)行的順序是連續(xù)且確定的,并永遠(yuǎn)不會(huì)并發(fā)。不同于系統(tǒng)線程,生成器只會(huì)在代碼中用 yield 標(biāo)記的地方才會(huì)懸掛。

好了。我們知道生成器是什么了。我們已經(jīng)看到了生成器器運(yùn)行,暫停,然后恢復(fù)執(zhí)行。現(xiàn)在的大問(wèn)題是,這樣奇怪的能力怎么可能是有用的呢?

生成器是迭代器

上周,我們已經(jīng)看到了 ES6 的迭代器不只是一個(gè)簡(jiǎn)單的內(nèi)置類。他們是語(yǔ)言的擴(kuò)展點(diǎn)。你可以通過(guò)實(shí)現(xiàn)兩種方法來(lái)創(chuàng)建你自己的迭代器,這兩種方法是:[ Symbol.iterator ]( )和 .next( )。

但是,實(shí)現(xiàn)接口總是至少還是有一點(diǎn)工作量的。讓我們來(lái)看看一個(gè)迭代器實(shí)現(xiàn)在實(shí)踐中看起來(lái)是什么樣的。因?yàn)槭且粋€(gè)例子,讓我們使用一個(gè)簡(jiǎn)單的范圍 ( range ) 迭代器,只簡(jiǎn)單的從一個(gè)數(shù)字?jǐn)?shù)到另一個(gè)數(shù)字,就像一個(gè)老式的 C 語(yǔ)言的 for ( ; ; ) 循環(huán)。

// 這個(gè)應(yīng)該三次發(fā)出“叮”的聲音
for (var value of range(0, 3)) {
  alert("Ding! at floor #" + value);
}

這里是一個(gè)使用 ES6 的類的解決方案。( 如果類的語(yǔ)言沒(méi)有完全的清楚,不要擔(dān)心——我們將在未來(lái)的博客中解決這個(gè)問(wèn)題。)

class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    } else {
      return {done: true, value: undefined};
    }
  }
}

// 返回一個(gè)新的從“開(kāi)始”數(shù)到“結(jié)束”的迭代器。
function range(start, stop) {
  return new RangeIterator(start, stop);
}

在實(shí)際運(yùn)行中看這段代碼 ( http://codepen.io/anon/pen/NqGgOQ )。

這就是像是在 Java 或 Swift 語(yǔ)言里實(shí)現(xiàn)一個(gè)迭代器。它不是那么糟糕。但它也并不是那么簡(jiǎn)單。在這個(gè)代碼中有沒(méi)有任何錯(cuò)誤?這就不好說(shuō)了。它看起來(lái)完全不像我們想在這里模仿的原來(lái)的 for ( ; ; ) 循環(huán):迭代器協(xié)議迫使我們拋棄了循環(huán)。

在這一點(diǎn)上,你可能會(huì)對(duì)迭代器不太熱情。他們可能對(duì)使用來(lái)說(shuō)很棒,但他們似乎很難實(shí)現(xiàn)。

你可能不會(huì)建議我們只是為了簡(jiǎn)單的創(chuàng)建迭代器,而在 JS 語(yǔ)言中引進(jìn)一個(gè)復(fù)雜的新的控制流結(jié)構(gòu)。但是,因?yàn)槲覀兇_實(shí)有生成器,我們能在這里使用它們嗎?讓我們?cè)囈辉嚕?/p>

function* range(start, stop) {
  for (var i = start; i < stop; i++)
    yield i;
}

在這里看代碼的具體運(yùn)行 ( http://codepen.io/anon/pen/mJewga ) 。

上述的四行的生成器是對(duì)先前 range( ) 的二十三行的實(shí)現(xiàn)的一個(gè)直接替代,包括了整個(gè) RangeIterator 類。這可能是因?yàn)樯善魇堑?。所有的生成器都有一個(gè)內(nèi)置的對(duì) .next() 已經(jīng) [Symbol.iterator]( )方法的實(shí)現(xiàn)。

不使用生成器來(lái)實(shí)現(xiàn)迭代器就像是被強(qiáng)迫用被動(dòng)語(yǔ)氣寫(xiě)一封很長(zhǎng)的郵件。本來(lái)想簡(jiǎn)單地想表達(dá)你的意思,可能到最后你說(shuō)的會(huì)變得相當(dāng)令人費(fèi)解。RangeIterator 是很長(zhǎng)且怪異的,因?yàn)樗仨毑皇褂醚h(huán)語(yǔ)法來(lái)描述一個(gè)循環(huán)的功能。生成器是答案。

我們還能如何使用生成器作為迭代器的能力?

  • 使對(duì)象可迭代。只要寫(xiě)一個(gè)迭代器函數(shù)來(lái)一直調(diào)用 this,在其出現(xiàn)的地方生成每一個(gè)值。然后以該對(duì)象的 [Symbol.iterator] 方法來(lái)安裝該生成器函數(shù)。
  • 簡(jiǎn)化數(shù)組構(gòu)建函數(shù)。實(shí)現(xiàn)一個(gè)函數(shù),每當(dāng)其被調(diào)用就會(huì)返回一個(gè)數(shù)組,如下面的這個(gè)例子:
// 將一維數(shù)組 'icons'分為長(zhǎng)度為 'rowLength'的數(shù)組。
function splitIntoRows(icons, rowLength) {
  var rows = [];
  for (var i = 0; i < icons.length; i += rowLength) {
    rows.push(icons.slice(i, i + rowLength));
  }
  return rows;
}

生成器可以將代碼縮短很多:

function* splitIntoRows(icons, rowLength) {
  for (var i = 0; i < icons.length; i += rowLength) {
    yield icons.slice(i, i + rowLength);
  }
}

在行為上唯一的不同之處在于,取代一次性計(jì)算所有的結(jié)果,并返回他們的一個(gè)數(shù)組,這里返回一個(gè)迭代器且其可以按需一個(gè)一個(gè)地計(jì)算結(jié)果。

  • 特別大量的結(jié)果。你不能構(gòu)建一個(gè)無(wú)窮大的數(shù)組。但是你可以返回一個(gè)生成器,其可以生成一個(gè)無(wú)限大的序列,同時(shí)每一個(gè)調(diào)用者都可以使用它不管他們需要多少個(gè)值。

  • 重構(gòu)復(fù)雜的循環(huán)。你有龐大而丑陋的函數(shù)嗎?你是不是想將它分為兩個(gè)簡(jiǎn)單的部分呢?生成器就是可以幫助你達(dá)成這一目標(biāo)的成套的重構(gòu)工具。當(dāng)你面對(duì)一個(gè)復(fù)雜的循環(huán),你可以將產(chǎn)生數(shù)據(jù)的代碼抽取出來(lái)編程一個(gè)獨(dú)立的生成器函數(shù)。然后改變循環(huán)為 for 循環(huán) ( myNewGenerator(args) 的 var 數(shù)據(jù))。

  • 使用迭代的工具。ES6 不提供擴(kuò)展的庫(kù)來(lái)進(jìn)行過(guò)濾,映射以及一般可以訪問(wèn)任意可迭代的數(shù)據(jù)集。但生成器是偉大的,你只需要用幾行代碼就可以構(gòu)建你需要的工具。

舉個(gè)例子說(shuō),假設(shè)你需要一個(gè)東西等同于 Array.prototype.filter,其是在 DOM NodeLists 上工作的,不只是一個(gè)數(shù)組。
這用代碼來(lái)實(shí)現(xiàn)就是小意思:

function* filter(test, iterable) {
  for (var item of iterable) {
    if (test(item))
      yield item;
  }
}

這個(gè)就是為什么生成器如此有用嗎?當(dāng)然。他們是要實(shí)現(xiàn)自定義的迭代器的簡(jiǎn)單的方法。同時(shí),迭代器在整個(gè) ES6 中是用于數(shù)據(jù)和循環(huán)的新標(biāo)準(zhǔn)。

但是,這還不是生成器能做的事情的全部。這甚至有可能不是他們做的最重要的事情。

生成器和異步代碼

這里是一個(gè) JS 代碼,我寫(xiě)了一個(gè) while 的 back 部分。

           };
        })
      });
    });
  });
});

可能這看起來(lái)和你代碼的一部分比較相像。 異步 API 通常情況下需要一個(gè)回調(diào),這就意味著你做一些事情時(shí)要寫(xiě)一個(gè)額外的匿名函數(shù)。所以如果你有一部分代碼做三件事情,而不是三行代碼,你是在看三個(gè)縮進(jìn)層次的代碼。

這里有一些我已經(jīng)寫(xiě)好的 JS 代碼:

}).on('close', function () {
  done(undefined, undefined);
}).on('error', function (error) {
  done(error);
});

異步 API 有錯(cuò)誤處理的約定,但不是使用異常。不同的 API 有不同的約定。在他們中的大多數(shù)中,錯(cuò)誤在默認(rèn)情況下被默默地刪除。其中有一些,即使是普通的圓滿完成,在默認(rèn)情況下都會(huì)被刪除。

直到現(xiàn)在,這些問(wèn)題都只是簡(jiǎn)單的轉(zhuǎn)畫(huà)為我們進(jìn)行異步編程的代價(jià)了。我們已經(jīng)開(kāi)始接受異步代碼了,他們只是看起來(lái)不是像同步代碼那樣美好和簡(jiǎn)單。

生成器提供了新的希望,可以不用這樣做的。

Q.async( ) 是一個(gè)試驗(yàn)性的嘗試。其使用迭代器來(lái)生成類似于同步代碼的異步代碼。
舉個(gè)例子:

// 
function makeNoise() {
  shake();
  rattle();
  roll();
}

// Asynchronous code to make some noise.
// Returns a Promise object that becomes resolved
// when we're done making noise.
function makeNoise_async() {
  return Q.async(function* () {
    yield shake_async();
    yield rattle_async();
    yield roll_async();
  });
}

主要的區(qū)別在于,異步版本必須在每個(gè)需要調(diào)用異步函數(shù)的地方添加 yield 關(guān)鍵字。

在 Q.async 版本中添加一點(diǎn)小東西,如 if 語(yǔ)句或 try/ catch 塊,與在普通同步版本中添加是完全相同的。相比于編寫(xiě)異步代碼的其他方式,有種不是在學(xué)習(xí)一個(gè)全新的語(yǔ)言的感覺(jué)。

如果你已經(jīng)遠(yuǎn)遠(yuǎn)得到了這些東西,你可能會(huì)喜歡上 James Long 的在這個(gè)話題上面的非常詳細(xì)的博客。

所以生成器指出了一個(gè)更適合人類大腦的新的異步編程模型。這項(xiàng)工作正在進(jìn)行中。除其他事項(xiàng)外,更好的語(yǔ)法可能有所幫助。

異步函數(shù)的提出,建立在雙方的承諾和生成器的基礎(chǔ)上,并從在 C#類似的功能中采取靈感,這些都是 ES7 要做的事情。

我什么時(shí)候可以用這瘋狂的東西?

在服務(wù)器端,我們現(xiàn)在可以在 io.js 中使用 ES6 生成器。
如果你啟用 --harmony 選項(xiàng),我們?cè)?Node 中可也已使用 ES6 生成器。

在瀏覽器中,現(xiàn)今只有火狐27版本以上以及谷歌瀏覽器39版本以上的支持 ES6 生成器。為了在現(xiàn)今的網(wǎng)絡(luò)上面使用生成器,你將需要使用 Babel 或者 Traceur 來(lái)將你的 ES6 的代碼翻譯為網(wǎng)絡(luò)友好的 ES5 代碼。

一些重要的事件值得了解:生成器是由布倫丹·艾希首次在 JS 上實(shí)現(xiàn)的。布倫丹·艾希的設(shè)計(jì)是緊緊跟隨由 Icon 啟發(fā)的 Python 生成器。他們?cè)缭?006年就運(yùn)用在火狐 2.0 版本上了。但是標(biāo)準(zhǔn)化的道路是崎嶇不平的,而且語(yǔ)法和行為在這個(gè)過(guò)程中改變了很多。ES6 生成器是由編程黑客溫格安迪在火狐瀏覽器和谷歌瀏覽器中實(shí)現(xiàn)的。這項(xiàng)工作是由 Bloomberg 贊助的。

yield

關(guān)于生成器還有更多的說(shuō)法。我們沒(méi)有包含 .throw() 和 .return() 方法,可選的參數(shù) .next(),或 yield* 表達(dá)式語(yǔ)法。但我認(rèn)為這個(gè)帖子已經(jīng)很長(zhǎng)了,且現(xiàn)在已經(jīng)足夠撲朔迷離了。像生成器本身,我們應(yīng)該停下來(lái)休息一下。

上一篇:模塊下一篇:代理