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