摘要:我們還能如何使用生成器作為迭代器的能力使對(duì)象可迭代。一些重要的事件值得了解生成器是由布倫丹艾希首次在上實(shí)現(xiàn)的。布倫丹艾希的設(shè)計(jì)是緊緊跟隨由啟發(fā)的生成器。
什么是生成器?
我們先從下面的這里例子開始。
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)用。
這個(gè)在一定程度上看起來像一個(gè)函數(shù),對(duì)不?這就被稱為生成器函數(shù),同時(shí)它與函數(shù)之間也有很多相似之處。但是你一下子就能發(fā)現(xiàn)兩個(gè)不同之處:
普通的函數(shù)使用function作為開始。生成器函數(shù)以function*開始。 在一個(gè)生成器函數(shù)中,yield是一個(gè)關(guān)鍵字,語法和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í)候,他們立即開始運(yùn)行,當(dāng)遇到 return或者throw的時(shí)候,他們停止運(yùn)行。任何一個(gè)JS程序員都非常習(xí)慣于上述的過程。
調(diào)用一個(gè)生成器看起來是一樣的:quips(”jorendorff”)。 但是當(dāng)你調(diào)用一個(gè)生成器,他還不開始運(yùn)行。反而,它返回一個(gè)暫停的生成器對(duì)象(在上述的例子中被稱為iter)。你可以認(rèn)為這個(gè)生成器對(duì)象是一個(gè)函數(shù)調(diào)用,暫時(shí)停止。特別的是,其在生成器函數(shù)一開始就停止了,在運(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)域是不確定的。
現(xiàn)在可能是一個(gè)好機(jī)會(huì)來返回到上面的對(duì)話貓的例子那頁面上,同時(shí)真正地可以玩轉(zhuǎn)代碼。嘗試著在一個(gè)循環(huán)中加入一個(gè)yield。這會(huì)發(fā)生什么呢?
在技術(shù)層面上,每一次一個(gè)生成器進(jìn)行yield操作,其堆棧楨,包括局部變量,參數(shù),臨時(shí)值,以及在生成器中的執(zhí)行的當(dāng)前位置,被從棧中刪除。然而,生成器對(duì)象保有這個(gè)堆棧幀的引用(或者是副本)。因此,接下來的調(diào)用.next( )可以重新激活它并繼續(xù)執(zhí)行。
值得指出的是,生成器都沒有線程。在能使用線程的語言中,多份代碼可以在同一時(shí)間運(yùn)行,這通常導(dǎo)致了競爭條件,非確定性和非常非常好的性能。生成器和這完全不同。當(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)在的大問題是,這樣奇怪的能力怎么可能是有用的呢?
生成器是迭代器,我們已經(jīng)看到了ES6的迭代器不只是一個(gè)簡單的內(nèi)置類。他們是語言的擴(kuò)展點(diǎn)。你可以通過實(shí)現(xiàn)兩種方法來創(chuàng)建你自己的迭代器,這兩種方法是:Symbol.iterator和.next()。
但是,實(shí)現(xiàn)接口總是至少還是有一點(diǎn)工作量的。讓我們來看看一個(gè)迭代器實(shí)現(xiàn)在實(shí)踐中看起來是什么樣的。因?yàn)槭且粋€(gè)例子,讓我們使用一個(gè)簡單的范圍(range)迭代器,只簡單的從一個(gè)數(shù)字?jǐn)?shù)到另一個(gè)數(shù)字,就像一個(gè)老式的C語言的for(;;)循環(huán)。
// 這個(gè)應(yīng)該三次發(fā)出“叮”的聲音 for (var value of range(0, 3)) { alert("Ding! at floor #" + value); }
這里是一個(gè)使用ES6的類的解決方案。
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è)新的從“開始”數(shù)到“結(jié)束”的迭代器。 function range(start, stop) { return new RangeIterator(start, stop); }
在實(shí)際運(yùn)行中看這段代碼(http://codepen.io/anon/pen/NqGgOQ)。
這就是像是在Java或Swift語言里實(shí)現(xiàn)一個(gè)迭代器。它不是那么糟糕。但它也并不是那么簡單。在這個(gè)代碼中有沒有任何錯(cuò)誤?這就不好說了。它看起來完全不像我們想在這里模仿的原來的for(;;)循環(huán):迭代器協(xié)議迫使我們拋棄了循環(huán)。
在這一點(diǎn)上,你可能會(huì)對(duì)迭代器不太熱情。他們可能對(duì)使用來說很棒,但他們似乎很難實(shí)現(xiàn)。
你可能不會(huì)建議我們只是為了簡單的創(chuàng)建迭代器,而在JS語言中引進(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)樯善魇堑鳌K械纳善鞫加幸粋€(gè)內(nèi)置的對(duì).next()已經(jīng)Symbol.iterator方法的實(shí)現(xiàn)。
不使用生成器來實(shí)現(xiàn)迭代器就像是被強(qiáng)迫用被動(dòng)語氣寫一封很長的郵件。本來想簡單地想表達(dá)你的意思,可能到最后你說的會(huì)變得相當(dāng)令人費(fèi)解。RangeIterator是很長且怪異的,因?yàn)樗仨毑皇褂醚h(huán)語法來描述一個(gè)循環(huán)的功能。生成器是答案。
我們還能如何使用生成器作為迭代器的能力?使對(duì)象可迭代。只要寫一個(gè)迭代器函數(shù)來一直調(diào)用this,在其出現(xiàn)的地方生成每一個(gè)值。然后以該對(duì)象的[Symbol.iterator]方法來安裝該生成器函數(shù)。
簡化數(shù)組構(gòu)建函數(shù)。實(shí)現(xiàn)一個(gè)函數(shù),每當(dāng)其被調(diào)用就會(huì)返回一個(gè)數(shù)組,如下面的這個(gè)例子:
// 將一維數(shù)組"icons"分為長度為"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è)無窮大的數(shù)組。但是你可以返回一個(gè)生成器,其可以生成一個(gè)無限大的序列,同時(shí)每一個(gè)調(diào)用者都可以使用它不管他們需要多少個(gè)值。
重構(gòu)復(fù)雜的循環(huán)。你有龐大而丑陋的函數(shù)嗎?你是不是想將它分為兩個(gè)簡單的部分呢?生成器就是可以幫助你達(dá)成這一目標(biāo)的成套的重構(gòu)工具。當(dāng)你面對(duì)一個(gè)復(fù)雜的循環(huán),你可以將產(chǎn)生數(shù)據(jù)的代碼抽取出來編程一個(gè)獨(dú)立的生成器函數(shù)。然后改變循環(huán)為for循環(huán)(myNewGenerator(args)的var數(shù)據(jù))。
使用迭代的工具。ES6不提供擴(kuò)展的庫來進(jìn)行過濾,映射以及一般可以訪問任意可迭代的數(shù)據(jù)集。但生成器是偉大的,你只需要用幾行代碼就可以構(gòu)建你需要的工具。舉個(gè)例子說,假設(shè)你需要一個(gè)東西等同于Array.prototype.filter,其是在DOM NodeLists上工作的,不只是一個(gè)數(shù)組。這用代碼來實(shí)現(xiàn)就是小意思:
function* filter(test, iterable) { for (var item of iterable) { if (test(item)) yield item; } }
這個(gè)就是為什么生成器如此有用嗎?當(dāng)然。他們是要實(shí)現(xiàn)自定義的迭代器的簡單的方法。同時(shí),迭代器在整個(gè)ES6中是用于數(shù)據(jù)和循環(huán)的新標(biāo)準(zhǔn)。
但是,這還不是生成器能做的事情的全部。這甚至有可能不是他們做的最重要的事情。
生成器和異步代碼這里是一個(gè) JS 代碼,我寫了一個(gè) while 的 back 部分。
}; }) }); }); }); });
可能這看起來和你代碼的一部分比較相像。異步API通常情況下需要一個(gè)回調(diào),這就意味著你做一些事情時(shí)要寫一個(gè)額外的匿名函數(shù)。所以如果你有一部分代碼做三件事情,而不是三行代碼,你是在看三個(gè)縮進(jìn)層次的代碼。
這里有一些我已經(jīng)寫好的 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)在,這些問題都只是簡單的轉(zhuǎn)畫為我們進(jìn)行異步編程的代價(jià)了。我們已經(jīng)開始接受異步代碼了,他們只是看起來不是像同步代碼那樣美好和簡單。
生成器提供了新的希望,可以不用這樣做的。
Q.async()是一個(gè)試驗(yàn)性的嘗試。其使用迭代器來生成類似于同步代碼的異步代碼。舉個(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語句或try/catch塊,與在普通同步版本中添加是完全相同的。相比于編寫異步代碼的其他方式,有種不是在學(xué)習(xí)一個(gè)全新的語言的感覺。
所以生成器指出了一個(gè)更適合人類大腦的新的異步編程模型。這項(xiàng)工作正在進(jìn)行中。除其他事項(xiàng)外,更好的語法可能有所幫助。
異步函數(shù)的提出,建立在雙方的承諾和生成器的基礎(chǔ)上,并從在C#類似的功能中采取靈感,這些都是ES7要做的事情。
我什么時(shí)候可以用這瘋狂的東西?在服務(wù)器端,我們現(xiàn)在可以在io.js中使用 ES6 生成器。如果你啟用--harmony選項(xiàng),我們?cè)贜ode中可也已使用ES6生成器。
在瀏覽器中,現(xiàn)今只有火狐27版本以上以及谷歌瀏覽器39版本以上的支持ES6生成器。為了在現(xiàn)今的網(wǎng)絡(luò)上面使用生成器,你將需要使用Babel或者Traceur來將你的ES6的代碼翻譯為網(wǎng)絡(luò)友好的ES5代碼。
一些重要的事件值得了解生成器是由布倫丹·艾希首次在JS上實(shí)現(xiàn)的。布倫丹·艾希的設(shè)計(jì)是緊緊跟隨由Icon啟發(fā)的Python生成器。他們?cè)缭?006年就運(yùn)用在火狐2.0版本上了。但是標(biāo)準(zhǔn)化的道路是崎嶇不平的,而且語法和行為在這個(gè)過程中改變了很多。ES6生成器是由編程黑客溫格安迪在火狐瀏覽器和谷歌瀏覽器中實(shí)現(xiàn)的。這項(xiàng)工作是由Bloomberg贊助的。
yield關(guān)于生成器還有更多的說法。我們沒有包含.throw()和.return()方法,可選的參數(shù).next(),或yield*表達(dá)式語法。但我認(rèn)為這個(gè)帖子已經(jīng)很長了,且現(xiàn)在已經(jīng)足夠撲朔迷離了。像生成器本身,我們應(yīng)該停下來休息一下。
轉(zhuǎn)載:http://www.html-js.com
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/79246.html
摘要:字面上是生成器的意思,在里是迭代器生成器,用于生成一個(gè)迭代器對(duì)象。當(dāng)執(zhí)行的時(shí)候,并不執(zhí)行函數(shù)體,而是返回一個(gè)迭代器。迭代器具有方法,每次調(diào)用方法,函數(shù)就執(zhí)行到語句的地方。也有觀點(diǎn)極力反對(duì),認(rèn)為隱藏了本身原型鏈的語言特性,使其更難理解。 本文為 ES6 系列的第一篇。旨在給新同學(xué)一些指引,帶大家走近 ES6 新特性。簡要介紹: 什么是 ES6 它有哪些明星特性 它可以運(yùn)行在哪些環(huán)境 ...
摘要:通過生成器創(chuàng)建的迭代器也是可迭代對(duì)象,因?yàn)樯善髂J(rèn)會(huì)為屬性賦值。我們可以用來訪問對(duì)象的默認(rèn)迭代器,例如對(duì)于一個(gè)數(shù)組獲得了數(shù)組這個(gè)可迭代對(duì)象的默認(rèn)迭代器,并操作它遍歷了數(shù)組中的元素。 ES6 新的數(shù)組方法、集合、for-of 循環(huán)、展開運(yùn)算符(...)甚至異步編程都依賴于迭代器(Iterator )實(shí)現(xiàn)。本文會(huì)詳解 ES6 的迭代器與生成器,并進(jìn)一步挖掘可迭代對(duì)象的內(nèi)部原理與使用方法 ...
摘要:引用自可迭代對(duì)象和迭代器不以規(guī)矩,不成方圓為了使某個(gè)對(duì)象成為可迭代對(duì)象象,它必須實(shí)現(xiàn)方法,也就是說,它得有一個(gè)是的屬性。的遍歷,絕對(duì)應(yīng)該用。 pseudo 英 [sju:d??] 美 [su:do?]adj.假的,虛偽的n.[口]假冒的人,偽君子 pseudo-array 英 [sju:d???re?] 美 [sju:d???re?][計(jì)] 偽數(shù)組 jQuery 對(duì)象是偽數(shù)組 兩個(gè)...
摘要:和命令和類似于中的的使用都是用來聲明變量的,只是都存在各自的特殊用法。解構(gòu)數(shù)組和對(duì)象是中最常用也是最重要表示形式。實(shí)例生成以后,可以用方法分別指定狀態(tài)和狀態(tài)的回調(diào)函數(shù)。這個(gè)迭代器對(duì)象擁有一個(gè)叫做的方法來幫助你重啟函數(shù)并得到下一個(gè)值。 let和const命令 let和const類似于javascript中的var的使用,都是用來聲明變量的,只是都存在各自的特殊用法。 在javascrip...
摘要:更新了個(gè)版本,最新正式版是語言的下一代標(biāo)準(zhǔn),早已在年月正式發(fā)布。基本不支持移動(dòng)端瀏覽器對(duì)的支持情況版起便可以支持的新特性。比較通用的工具方案有,,,等。 1、ECMAScript是什么? 和 JavaScript 有著怎樣的關(guān)系? 1996 年 11 月,Netscape 創(chuàng)造了javascript并將其提交給了標(biāo)準(zhǔn)化組織 ECMA,次年,ECMA 發(fā)布 262 號(hào)標(biāo)準(zhǔn)文件(ECMA-...
摘要:本質(zhì)就是一個(gè)編譯器,通過將源代碼解析成抽象語法樹將源代碼的結(jié)果一系列轉(zhuǎn)換生成目標(biāo)代碼的將目標(biāo)代碼的轉(zhuǎn)換成代碼。項(xiàng)目構(gòu)建三開發(fā)環(huán)境本地服務(wù)器搭建源碼下載地址參考資料入門阮一峰中文文檔中文網(wǎng) 注:以下教程均在 windows 環(huán)境實(shí)現(xiàn),使用其他操作系統(tǒng)的同學(xué)實(shí)踐過程可能會(huì)有些出入。 ??在上一章 webpack 項(xiàng)目構(gòu)建:(一)基本架構(gòu)搭建 我們搭建了一個(gè)最基本的 webpack 項(xiàng)目,現(xiàn)...
閱讀 2955·2021-11-23 09:51
閱讀 1670·2021-10-15 09:39
閱讀 1061·2021-08-03 14:03
閱讀 2893·2019-08-30 15:53
閱讀 3441·2019-08-30 15:52
閱讀 2492·2019-08-29 16:17
閱讀 2795·2019-08-29 16:12
閱讀 1652·2019-08-29 15:26