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