欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解JavaScript ES6中的Generator

 更新時(shí)間:2015年07月28日 09:58:46   作者:Jason Orendorff  
這篇文章主要介紹了詳解JavaScript ES6中的Generator,ES6版本的JS帶來了諸多簡(jiǎn)潔化的改善,需要的朋友可以參考下

今天討論的新特性讓我非常興奮,因?yàn)檫@個(gè)特性是 ES6 中最神奇的特性。

這里的“神奇”意味著什么呢?對(duì)于初學(xué)者來說,該特性與以往的 JS 完全不同,甚至有些晦澀難懂。從某種意義上說,它完全改變了這門語言的通常行為,這不是“神奇”是什么呢。

不僅如此,該特性還可以簡(jiǎn)化程序代碼,將復(fù)雜的“回調(diào)堆棧”改成直線執(zhí)行的形式。

我是不是鋪墊的太多了?下面開始深入介紹,你自己去判斷吧。
簡(jiǎn)介

什么是 Generator?

看下面代碼:

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!";
}
 
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!";
}

上面代碼是模仿Talking cat(當(dāng)下一個(gè)非常流行的應(yīng)用)的一部分,點(diǎn)擊這里試玩,如果你對(duì)代碼感到困惑,那就回到這里來看下面的解釋。

這看上去很像一個(gè)函數(shù),這被稱為 Generator 函數(shù),它與我們常見的函數(shù)有很多共同點(diǎn),但還可以看到下面兩個(gè)差異:

    通常的函數(shù)以 function 開始,但 Generator 函數(shù)以 function* 開始。
    在 Generator 函數(shù)內(nèi)部,yield 是一個(gè)關(guān)鍵字,和 return 有點(diǎn)像。不同點(diǎn)在于,所有函數(shù)(包括 Generator 函數(shù))都只能返回一次,而在 Generator 函數(shù)中可以 yield 任意次。yield 表達(dá)式暫停了 Generator 函數(shù)的執(zhí)行,然后可以從暫停的地方恢復(fù)執(zhí)行。

常見的函數(shù)不能暫停執(zhí)行,而 Generator 函數(shù)可以,這就是這兩者最大的區(qū)別。
原理

調(diào)用 quips() 時(shí)發(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 }

 
> 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 }

我們對(duì)普通函數(shù)的行為非常熟悉,函數(shù)被調(diào)用時(shí)就立即執(zhí)行,直到函數(shù)返回或拋出一個(gè)異常,這是所有 JS 程序員的第二天性。

Generator 函數(shù)的調(diào)用方法與普通函數(shù)一樣:quips("jorendorff"),但調(diào)用一個(gè) Generator 函數(shù)時(shí)并沒有立即執(zhí)行,而是返回了一個(gè) Generator 對(duì)象(上面代碼中的 iter),這時(shí)函數(shù)就立即暫停在函數(shù)代碼的第一行。

每次調(diào)用 Generator 對(duì)象的 .next() 方法時(shí),函數(shù)就開始執(zhí)行,直到遇到下一個(gè) yield 表達(dá)式為止。

這就是為什么我們每次調(diào)用 iter.next() 時(shí)都會(huì)得到一個(gè)不同的字符串,這些都是在函數(shù)內(nèi)部通過 yield 表達(dá)式產(chǎn)生的值。

當(dāng)執(zhí)行最后一個(gè) iter.next() 時(shí),就到達(dá)了 Generator 函數(shù)的末尾,所以返回結(jié)果的 .done屬性值為 true,并且 .value 屬性值為 undefined。

現(xiàn)在,回到 Talking cat 的 DEMO,嘗試在代碼中添加一些 yield 表達(dá)式,看看會(huì)發(fā)生什么。

從技術(shù)層面上講,每當(dāng) Generator 函數(shù)執(zhí)行遇到 yield 表達(dá)式時(shí),函數(shù)的棧幀 — 本地變量,函數(shù)參數(shù),臨時(shí)值和當(dāng)前執(zhí)行的位置,就從堆棧移除,但是 Generator 對(duì)象保留了對(duì)該棧幀的引用,所以下次調(diào)用 .next() 方法時(shí),就可以恢復(fù)并繼續(xù)執(zhí)行。

值得提醒的是 Generator 并不是多線程。在支持多線程的語言中,同一時(shí)間可以執(zhí)行多段代碼,并伴隨著執(zhí)行資源的競(jìng)爭(zhēng),執(zhí)行結(jié)果的不確定性和較好的性能。而 Generator 函數(shù)并不是這樣,當(dāng)一個(gè) Generator 函數(shù)執(zhí)行時(shí),它與其調(diào)用者都在同一線程中執(zhí)行,每次執(zhí)行順序都是確定的,有序的,并且執(zhí)行順序不會(huì)發(fā)生改變。與線程不同,Generator 函數(shù)可以在內(nèi)部的 yield 的標(biāo)志點(diǎn)暫停執(zhí)行。

通過介紹 Generator 函數(shù)的暫停、執(zhí)行和恢復(fù)執(zhí)行,我們知道了什么是 Generator 函數(shù),那么現(xiàn)在拋出一個(gè)問題:Generator 函數(shù)到底有什么用呢?
迭代器

通過上篇文章,我們知道迭代器并不是 ES6 的一個(gè)內(nèi)置的類,而只是作為語言的一個(gè)擴(kuò)展點(diǎn),你可以通過實(shí)現(xiàn) [Symbol.iterator]() 和 .next() 方法來定義一個(gè)迭代器。

但是,實(shí)現(xiàn)一個(gè)接口還是需要寫一些代碼的,下面我們來看看在實(shí)際中如何實(shí)現(xiàn)一個(gè)迭代器,以實(shí)現(xiàn)一個(gè) range 迭代器為例,該迭代器只是簡(jiǎn)單地從一個(gè)數(shù)累加到另一個(gè)數(shù),有點(diǎn)像 C 語言中的 for (;;) 循環(huán)。

// This should "ding" three times
for (var value of range(0, 3)) {
 alert("Ding! at floor #" + value);
}
 
// This should "ding" three times
for (var value of range(0, 3)) {
 alert("Ding! at floor #" + value);
}

現(xiàn)在有一個(gè)解決方案,就是使用 ES6 的類。(如果你對(duì) class 語法還不熟悉,不要緊,我會(huì)在將來的文章中介紹。)

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};
  }
 }
}

// Return a new iterator that counts up from 'start' to 'stop'.
function range(start, stop) {
 return new RangeIterator(start, stop);
}
 
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};
  }
 }
}
 
// Return a new iterator that counts up from 'start' to 'stop'.
function range(start, stop) {
 return new RangeIterator(start, stop);
}

查看該 DEMO。

這種實(shí)現(xiàn)方式與 Java 和 Swift 的實(shí)現(xiàn)方式類似,看上去還不錯(cuò),但還不能說上面代碼就完全正確,代碼沒有任何 Bug?這很難說。我們看不到任何傳統(tǒng)的 for (;;) 循環(huán)代碼:迭代器的協(xié)議迫使我們將循環(huán)拆散了。

在這一點(diǎn)上,你也許會(huì)對(duì)迭代器不那么熱衷了,它們使用起來很方便,但是實(shí)現(xiàn)起來似乎很難。

我們可以引入一種新的實(shí)現(xiàn)方式,以使得實(shí)現(xiàn)迭代器更加容易。上面介紹的 Generator 可以用在這里嗎?我們來試試:

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

查看該 DEMO。

上面這 4 行代碼就可以完全替代之前的那個(gè) 23 行的實(shí)現(xiàn),替換掉整個(gè) RangeIterator 類,這是因?yàn)?Generator 天生就是迭代器,所有的 Generator 都原生實(shí)現(xiàn)了 .next() 和 [Symbol.iterator]() 方法。你只需要實(shí)現(xiàn)其中的循環(huán)邏輯就夠了。

不使用 Generator 去實(shí)現(xiàn)一個(gè)迭代器就像被迫寫一個(gè)很長(zhǎng)很長(zhǎng)的郵件一樣,本來簡(jiǎn)單的表達(dá)出你的意思就可以了,RangeIterator 的實(shí)現(xiàn)是冗長(zhǎng)和令人費(fèi)解的,因?yàn)樗鼪]有使用循環(huán)語法去實(shí)現(xiàn)一個(gè)循環(huán)功能。使用 Generator 才是我們需要掌握的實(shí)現(xiàn)方式。

我們可以使用作為迭代器的 Generator 的哪些功能呢?

    使任何對(duì)象可遍歷 — 編寫一個(gè) Genetator 函數(shù)去遍歷 this,每遍歷到一個(gè)值就 yield 一下,然后將該 Generator 函數(shù)作為要遍歷的對(duì)象上的 [Symbol.iterator] 方法的實(shí)現(xiàn)。
    簡(jiǎn)化返回?cái)?shù)組的函數(shù) — 假如有一個(gè)每次調(diào)用時(shí)都返回一個(gè)數(shù)組的函數(shù),比如:

// Divide the one-dimensional array 'icons'
// into arrays of length 'rowLength'.
function splitIntoRows(icons, rowLength) {
 var rows = [];
 for (var i = 0; i < icons.length; i += rowLength) {
  rows.push(icons.slice(i, i + rowLength));
 }
 return rows;
}

 
// Divide the one-dimensional array 'icons'
// into arrays of length 'rowLength'.
function splitIntoRows(icons, rowLength) {
 var rows = [];
 for (var i = 0; i < icons.length; i += rowLength) {
  rows.push(icons.slice(i, i + rowLength));
 }
 return rows;
}

使用 Generator 可以簡(jiǎn)化這類函數(shù):

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

這兩者唯一的區(qū)別在于,前者在調(diào)用時(shí)計(jì)算出了所有結(jié)果并用一個(gè)數(shù)組返回,后者返回的是一個(gè)迭代器,結(jié)果是在需要的時(shí)候才進(jìn)行計(jì)算,然后一個(gè)一個(gè)地返回。

    無窮大的結(jié)果集 — 我們不能構(gòu)建一個(gè)無窮大的數(shù)組,但是我們可以返回一個(gè)生成無盡序列的 Generator,并且每個(gè)調(diào)用者都可以從中獲取到任意多個(gè)需要的值。
    重構(gòu)復(fù)雜的循環(huán) — 你是否想將一個(gè)復(fù)雜冗長(zhǎng)的函數(shù)重構(gòu)為兩個(gè)簡(jiǎn)單的函數(shù)?Generator 是你重構(gòu)工具箱中一把新的瑞士軍刀。對(duì)于一個(gè)復(fù)雜的循環(huán),我們可以將生成數(shù)據(jù)集那部分代碼重構(gòu)為一個(gè) Generator 函數(shù),然后用 for-of 遍歷:for (var data of myNewGenerator(args))。
    構(gòu)建迭代器的工具 — ES6 并沒有提供一個(gè)可擴(kuò)展的庫,來對(duì)數(shù)據(jù)集進(jìn)行 filter 和 map等操作,但 Generator 可以用幾行代碼就實(shí)現(xiàn)這類功能。

例如,假設(shè)你需要在 Nodelist 上實(shí)現(xiàn)與 Array.prototype.filter 同樣的功能的方法。小菜一碟的事:

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

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

所以,Generator 很實(shí)用吧?當(dāng)然,這是實(shí)現(xiàn)自定義迭代器最簡(jiǎn)單直接的方式,并且,在 ES6 中,迭代器是數(shù)據(jù)集和循環(huán)的新標(biāo)準(zhǔn)。

但,這還不是 Generator 的全部功能。
異步代碼

異步 API 通常都需要一個(gè)回調(diào)函數(shù),這意味著每次你都需要編寫一個(gè)匿名函數(shù)來處理異步結(jié)果。如果同時(shí)處理三個(gè)異步事務(wù),我們看到的是三個(gè)縮進(jìn)層次的代碼,而不僅僅是三行代碼。

看下面代碼:

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

異步 API 通常都有錯(cuò)誤處理的約定,不同的 API 有不同的約定。大多數(shù)情況下,錯(cuò)誤是默認(rèn)丟棄的,甚至有些將成功也默認(rèn)丟棄了。

直到現(xiàn)在,這些問題仍是我們處理異步編程必須付出的代價(jià),而且我們也已經(jīng)接受了異步代碼只是看不來不像同步代碼那樣簡(jiǎn)單和友好。

Generator 給我們帶來了希望,我們可以不再采用上面的方式。

Q.async()是一個(gè)將 Generator 和 Promise 結(jié)合起來處理異步代碼的實(shí)驗(yàn)性嘗試,讓我們的異步代碼類似于相應(yīng)的同步代碼。

例如:

// Synchronous code to make some noise.
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();
 });
}
 
// Synchronous code to make some noise.
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)用的前面添加 yield 關(guān)鍵字。

在 Q.async 中,添加一個(gè) if 語句或 try-catch 異常處理,就和在同步代碼中的方式一樣,與其他編寫異步代碼的方式相比,減少了很多學(xué)習(xí)成本。

Generator 為我們提供了一種更適合人腦思維方式的異步編程模型。但更好的語法也許更有幫助,在 ES7 中,一個(gè)基于 Promise 和 Generator 的異步處理函數(shù)正在規(guī)劃之中,靈感來自 C# 中類似的特性。
兼容性

在服務(wù)器端,現(xiàn)在就可以直接在 io.js 中使用 Generator(或者在 NodeJs 中以 --harmony 啟動(dòng)參數(shù)來啟動(dòng) Node)。

在瀏覽器端,目前只有 Firefox 27 和 Chrome 39 以上的版本才支持 Generator,如果想直接在 Web 上使用,你可以使用 Babel 或 Google 的 Traceur 將 ES6 代碼轉(zhuǎn)換為 Web 友好的 ES5 代碼。

一些題外話:JS 版本的 Generator 最早是由 Brendan Eich 實(shí)現(xiàn),他借鑒了 Python Generator的實(shí)現(xiàn),該實(shí)現(xiàn)的靈感來自 Icon,早在 2006 年的 Firefox 2.0 就吸納了 Generator。但標(biāo)準(zhǔn)化的道路是坎坷的,一路下來,其語法和行為都發(fā)生了很多改變,F(xiàn)irefox 和 Chrome 中的 ES6 Generator 是由 Andy Wingo 實(shí)現(xiàn) ,這項(xiàng)工作是由 Bloomberg 贊助的。
yield;

關(guān)于 Generator 還有一些未提及的部分,我們還沒有涉及到 .throw() 和 .return() 方法的使用,.next() 方法的可選參數(shù),還有 yield* 語法。但我認(rèn)為這篇文章已經(jīng)夠長(zhǎng)了,就像 Generator 一樣,我們也暫停一下,另外找個(gè)時(shí)間再剩余的部分。

我們已經(jīng)介紹了 ES6 中兩個(gè)非常重要的特性,那么現(xiàn)在可以大膽地說,ES6 將改變我們的生活,看似簡(jiǎn)單的特性,卻有極大的用處。

相關(guān)文章

最新評(píng)論