摘要:迭代器的出現(xiàn)旨在消除這種復(fù)雜性并減少循環(huán)中的錯誤。返回一個迭代器,其值為集合的值。在迭代器中拋出錯誤除了給迭代器傳遞數(shù)據(jù)外,還可以給它傳遞錯誤條件。通過方法,當(dāng)?shù)骰謴?fù)執(zhí)行時可令其拋出一個錯誤。
循環(huán)語句的問題
var colors = ["red", "green", "blue"]; for(var i=0; i在ES6之前,這種標(biāo)準(zhǔn)的for循環(huán),通過變量來跟蹤數(shù)組的索引。如果多個循環(huán)嵌套就需要追蹤多個變量,代碼復(fù)雜度會大大增加,也容易產(chǎn)生錯用循環(huán)變量的bug。
迭代器的出現(xiàn)旨在消除這種復(fù)雜性并減少循環(huán)中的錯誤。
什么是迭代器我們先感受一下用ES5語法模擬創(chuàng)建一個迭代器:
function createIterator(items) { var i = 0; return { // 返回一個迭代器對象 next: function() { // 迭代器對象一定有個next()方法 var done = (i >= items.length); var value = !done ? items[i++] : undefined; return { // next()方法返回結(jié)果對象 value: value, done: done }; } }; } var iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false}" console.log(iterator.next()); // "{ value: 2, done: false}" console.log(iterator.next()); // "{ value: 3, done: false}" console.log(iterator.next()); // "{ value: undefiend, done: true}" // 之后所有的調(diào)用都會返回相同內(nèi)容 console.log(iterator.next()); // "{ value: undefiend, done: true}"以上,我們通過調(diào)用createIterator()函數(shù),返回一個對象,這個對象存在一個next()方法,當(dāng)next()方法被調(diào)用時,返回格式{ value: 1, done: false}的結(jié)果對象。
因此,我們可以這么定義:迭代器是一個擁有next()方法的特殊對象,每次調(diào)用next()都返回一個結(jié)果對象。借助這個迭代器對象,我們來改造剛開始那個標(biāo)準(zhǔn)的for循環(huán)【暫時先忘記ES6的for-of循環(huán)新特性】:
var colors = ["red", "green", "blue"]; var iterator = createIterator(colors); while(!iterator.next().done){ console.log(iterator.next().value); }what?,消除循環(huán)變量而已,需要搞這么麻煩,代碼上不是得不償失了嗎?
什么是生成器
并非如此,畢竟createIterator()只需寫一次,就可以一直復(fù)用。不過ES6引入了生成器對象,可以讓創(chuàng)建迭代器的過程變得更加簡單。生成器是一種返回迭代器的函數(shù),通過function關(guān)鍵字后的星號(*)來表示,函數(shù)中會用到新的關(guān)鍵字yield。
function *createIterator(items) { for(let i=0; i上面,我們用ES6的生成器,大大簡化了迭代器的創(chuàng)建過程。我們給生成器函數(shù)createIterator()傳入一個items數(shù)組,函數(shù)內(nèi)部,for循環(huán)不斷從數(shù)組中生成新的元素放入迭代器中,每遇到一個yield語句循環(huán)都會停止;每次調(diào)用迭代器的next()方法,循環(huán)便繼續(xù)運行并停止在下一條yield語句處。
生成器的創(chuàng)建方式生成器是個函數(shù):
function *createIterator(items) { ... }可以用函數(shù)表達式方式書寫:
let createIterator = function *(item) { ... }也可以添加到對象中,ES5風(fēng)格對象字面量:
let o = { createIterator: function *(items) { ... } }; let iterator = o.createIterator([1, 2, 3]);ES6風(fēng)格的對象方法簡寫方式:
let o = { *createIterator(items) { ... } }; let iterator = o.createIterator([1, 2, 3]);可迭代(Iterable)對象在ES6中,所有的集合對象(數(shù)組、Set集合及Map集合)和字符串都是可迭代對象,可迭代對象都綁定了默認的迭代器。
來了來了,姍姍來遲的ES6循環(huán)新特性for-of:
var colors = ["red", "green", "blue"]; for(let color of colors){ console.log(color); }for-of循環(huán),可作用在可迭代對象上,正是利用了可迭代對象上的默認迭代器。大致過程是:for-of循環(huán)每執(zhí)行一次都會調(diào)用可迭代對象的next()方法,并將迭代器返回的結(jié)果對象的value屬性存儲在變量中,循環(huán)將繼續(xù)執(zhí)行這一過程直到返回對象的done屬性的值為true。
如果只需要迭代數(shù)組或集合中的值,用for-of循環(huán)代替for循環(huán)是個不錯的選擇。
訪問默認迭代器可迭代對象,都有一個Symbol.iterator方法,for-of循環(huán)時,通過調(diào)用colors數(shù)組的Symbol.iterator方法來獲取默認迭代器的,這一過程是在JavaScript引擎背后完成的。
我們可以主動獲取一下這個默認迭代器來感受一下:
let values = [1, 2, 3]; let iterator = values[Symbol.iterator](); console.log(iterator.next()); // "{ value: 1, done: false}" console.log(iterator.next()); // "{ value: 2, done: false}" console.log(iterator.next()); // "{ value: 3, done: false}" console.log(iterator.next()); // "{ value: undefined, done: true}"在這段代碼中,通過Symbol.iterator獲取了數(shù)組values的默認迭代器,并用它遍歷數(shù)組中的元素。在JavaScript引擎中執(zhí)行for-of循環(huán)語句也是類似的處理過程。
用Symbol.iterator屬性來檢測對象是否為可迭代對象:
function isIterator(object) { return typeof object[Symbol.iterator] === "function"; } console.log(isIterable([1, 2, 3])); // true console.log(isIterable(new Set())); // true console.log(isIterable(new Map())); // true console.log(isIterable("Hello")); // true創(chuàng)建可迭代對象當(dāng)我們在創(chuàng)建對象時,給Symbol.iterator屬性添加一個生成器,則可以將其變成可迭代對象:
let collection = { items: [], *[Symbol.iterator]() { // 將生成器賦值給對象的Symbol.iterator屬性來創(chuàng)建默認的迭代器 for(let item of this.items) { yield item; } } }; collection.items.push(1); collection.items.push(2); collection.items.push(3); for(let x of collection) { console.log(x); }內(nèi)建迭代器ES6中的集合對象,數(shù)組、Set集合和Map集合,都內(nèi)建了三種迭代器:
entries() 返回一個迭代器,其值為多個鍵值對。
如果是數(shù)組,第一個元素是索引位置;如果是Set集合,第一個元素與第二個元素一樣,都是值。values() 返回一個迭代器,其值為集合的值。
keys() 返回一個迭代器,其值為集合中的所有鍵名。
不同集合的默認迭代器
如果是數(shù)組,返回的是索引;如果是Set集合,返回的是值(Set的值被同時用作鍵和值)。每個集合類型都有一個默認的迭代器,在for-of循環(huán)中,如果沒有顯式指定則使用默認的迭代器。按常規(guī)使用習(xí)慣,我們很容易猜到,數(shù)組和Set集合的默認迭代器是values(),Map集合的默認迭代器是entries()。
請看以下示例:
let colors = [ "red", "green", "blue"]; let tracking = new Set([1234, 5678, 9012]); let data = new Map(); data.set("title", "Understanding ECMAScript 6"); data.set("format", "print"); // 與調(diào)用colors.values()方法相同 for(let value of colors) { console.log(value); } // 與調(diào)用tracking.values()方法相同 for(let num of tracking) { console.log(num); } // 與調(diào)用data.entries()方法相同 for(let entry of data) { console.log(entry); }這段代碼會輸入以下內(nèi)容:
"red" "green" "blue" 1234 5678 9012 ["title", "Understanding ECMAScript 6"] ["format", "print"]for-of循環(huán)配合解構(gòu)特性,操縱數(shù)據(jù)會更方便:
for(let [key, value] of data) { console.log(key + "=" + value); }用展開運算符操縱let set = new Set([1, 2, 3, 4, 5]), array = [...set]; console.log(array); // [1,2,3,4,5]展開運算符可以操作所有的可迭代對象,并根據(jù)默認迭代器來選取要引用的值,從迭代器讀取所有值。然后按返回順序?qū)⑺鼈円来尾迦氲綌?shù)組中。因此如果想將可迭代對象轉(zhuǎn)換為數(shù)組,用展開運算符是最簡單的方法。
迭代器高級功能 給迭代器傳參前面我們看到,在迭代器內(nèi)部使用yield關(guān)鍵字可以生成值,在外面可以用迭代器的next()方法獲得返回值。
其實next()方法還可以接收參數(shù),這個參數(shù)的值就會代替生成器內(nèi)部上一條yield語句的返回值。function *createIterator() { let first = yield 1; let second = yield first + 2; // 4 + 2 yield second + 3; // 5 + 3 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.next(5)); // "{ value: 8, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"下圖的陰影展示了每次yield前正在執(zhí)行的代碼,可以輔助理解程序內(nèi)部的具體細節(jié):
在迭代器中拋出錯誤
在生成器內(nèi)部,淺紅色高亮的是next()方法的第一次調(diào)用,淺綠色標(biāo)識了next(4)的調(diào)用過程,紫色標(biāo)示了next(5)的調(diào)用過程,分別返回每一次yield生成的值。這里有一個過程很復(fù)雜,在執(zhí)行左側(cè)代碼前,右側(cè)的每一個表達式會先執(zhí)行再停止。
這里有個特例,第一次調(diào)用next()方法時無論傳入什么參數(shù)都會被丟棄。由于傳遞給next()方法的參數(shù)會代替上一次yield的返回值,而在第一次調(diào)用next()方法前不會執(zhí)行任何yield語句,因此在第一次調(diào)用next()方法時傳遞參數(shù)是毫無意義的。除了給迭代器傳遞數(shù)據(jù)外,還可以給它傳遞錯誤條件。通過throw()方法,當(dāng)?shù)骰謴?fù)執(zhí)行時可令其拋出一個錯誤。
function *createIterator() { let first = yield 1; let second = yield first + 2; // yield 4 + 2, 然后拋出錯誤 yield second + 3; // 永遠不會被執(zhí)行 } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.throw(new Error("Boom"))); // 從生成器中拋出錯誤這個示例中,前兩個表達式正常求值,而調(diào)用throw()后,在繼續(xù)執(zhí)行l(wèi)et second求值前,錯誤就會被拋出并阻止代碼繼續(xù)執(zhí)行。
知道了這一點,就可以在生成器內(nèi)部通過try-catch代碼塊來捕獲這些錯誤:function *createIterator() { let first = yield 1; let second; try { second = yield first + 2; // yield 4 + 2, 然后拋出錯誤 } catch(e) { second = 6; } yield second + 3; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next(4)); // "{ value: 6, done: false }" console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"這里有個有趣的現(xiàn)象:調(diào)用throw()方法后也會像調(diào)用next()方法一樣返回一個結(jié)果對象。由于在生成器內(nèi)部捕獲了這個錯誤,因而會繼續(xù)執(zhí)行下一條yield語句,最終返回數(shù)值9。
生成器返回語句
如此一來,next()和throw()就像是迭代器的兩條指令,調(diào)用next()方法命令迭代器繼續(xù)執(zhí)行(可能提供一個值),調(diào)用throw()方法也會命令迭代器繼續(xù)執(zhí)行,但同時拋出一個錯誤,在此之后的執(zhí)行過程取決于生成器內(nèi)部的代碼。由于生成器也是函數(shù),因此可以通過return語句提前退出函數(shù)執(zhí)行。
function *createIterator() { yield 1; return; yield 2; yield 3; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"這段代碼中的生成器包含多條yield語句和一條return語句,其中return語句緊隨第一條yield語句,其后的yield語句將不會被執(zhí)行。
在return語句中也可以指定一個返回值:function *createIterator() { yield 1; return 10; } let iterator = createIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 10, done: true }" console.log(iterator.next()); // "{ value: undefined, done: true }"通過return語句指定的返回值,只會在返回對象中出現(xiàn)一次,在后續(xù)調(diào)用返回的對象中,value屬性會被重置為undefined。
委托生成器在某些情況下,需要將兩個迭代器合二為一,這時可以創(chuàng)建一個生成器,再給yield語句添加一個星號,就可以將生成數(shù)據(jù)的過程委托給其他生成器。
function *createNumberIterator() { yield 1; yield 2; } function *createColorIterator() { yield "red"; yield "green"; } function *createCombinedIterator() { yield *createNumberIterator(); yield *createColorIterator(); yield true; } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "red", done: false }" console.log(iterator.next()); // "{ value: "green", done: false }" console.log(iterator.next()); // "{ value: true, done: false }" console.log(iterator.next()); // "{ value: undfined, done: true }"有了委托生成器這個信功能,你可以進一步利用生成器的返回值來處理復(fù)雜任務(wù):
function *createNumberIterator() { yield 1; yield 2; return 3; } function *createRepeatingIterator(count) { for(let i=0; i注意,無論通過何種方式調(diào)用迭代器的next()方法,數(shù)值3永遠不會被返回,它只存在于生成器createCombinedIterator()的內(nèi)部。但如果想輸出這個值,則可以額外添加一條yield語句:
function *createNumberIterator() { yield 1; yield 2; return 3; } function *createRepeatingIterator(count) { for(let i=0; i
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/87370.html
摘要:我個人認為迭代器和生成器是新增的特性里面,非常重要的部分,充分地掌握和使用迭代器和生成器,是十分必要和重要的,所以我會寫關(guān)于二者的一系列文章。 我個人認為迭代器和生成器是es6新增的特性里面,非常重要的部分,充分地掌握和使用迭代器和生成器,是十分必要和重要的,所以我會寫關(guān)于二者的一系列文章。話不多說,先來了解一下基本概念:一:什么是迭代器 1: 迭代器是一個對象 2: 迭代器有一個屬性...
摘要:引用自可迭代對象和迭代器不以規(guī)矩,不成方圓為了使某個對象成為可迭代對象象,它必須實現(xiàn)方法,也就是說,它得有一個是的屬性。的遍歷,絕對應(yīng)該用。 pseudo 英 [sju:d??] 美 [su:do?]adj.假的,虛偽的n.[口]假冒的人,偽君子 pseudo-array 英 [sju:d???re?] 美 [sju:d???re?][計] 偽數(shù)組 jQuery 對象是偽數(shù)組 兩個...
摘要:迭代器和生成器將迭代的概念直接帶入核心語言,并提供一種機制來自定義循環(huán)的行為。本文主要會介紹中新增的迭代器和生成器。屬性本身是函數(shù),是當(dāng)前數(shù)據(jù)結(jié)構(gòu)默認的迭代器生成函數(shù)。 本文是 重溫基礎(chǔ) 系列文章的第十三篇。今日感受:每次自我年終總結(jié),都會有各種情緒和收獲。 系列目錄: 【復(fù)習(xí)資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎(chǔ)】1.語法和數(shù)據(jù)類型 【重溫基礎(chǔ)】2.流...
摘要:在生成器中使用語句生成器也是函數(shù),所以它也可以使用語句。只是由于生成器本身的特性,其內(nèi)部的的行為會和一般函數(shù)有些差別。 前面2篇系列文章講解了迭代器和生成器的最常用,最基礎(chǔ)的用法;這篇來討論迭代器和生成器的一些稍稍高級一點的用法: 1: 給迭代器的next()方法傳參 2: 從迭代器中拋出錯誤 3: 在生成器中使用return語句 4: 委托生成器(組合生成器或者生成器組合?) 1: ...
摘要:沒有顯示顯示顯示關(guān)鍵字迭代器生成器用于馬上退出代碼塊并保留現(xiàn)場,當(dāng)執(zhí)行迭代器的函數(shù)時,則能從退出點恢復(fù)現(xiàn)場并繼續(xù)執(zhí)行下去。迭代器迭代器是一個擁有方法和方法的對象,通過函數(shù)不斷執(zhí)行以關(guān)鍵字分割的代碼段,通過函數(shù)令分割的代碼段拋出異常。 一、前言 第一次看koajs的示例時,發(fā)現(xiàn)該語句 function *(next){..........
閱讀 2042·2023-04-26 01:33
閱讀 1669·2023-04-26 00:52
閱讀 1052·2021-11-18 13:14
閱讀 5466·2021-09-26 10:18
閱讀 2919·2021-09-22 15:52
閱讀 1498·2019-08-29 17:15
閱讀 3028·2019-08-29 16:11
閱讀 1046·2019-08-29 16:11