var colors = ["red", "green", "blue"]; for(var i=0; i<colors.length; i++){ console.log(colors[i]); }
在ES6
以前,這種標準的for循環,經過變量來跟蹤數組的索引。若是多個循環嵌套就須要追蹤多個變量,代碼複雜度會大大增長,也容易產生錯用循環變量的bug。javascript
迭代器的出現旨在消除這種複雜性並減小循環中的錯誤。java
咱們先感覺一下用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循環新特性】:this
var colors = ["red", "green", "blue"]; var iterator = createIterator(colors); while(!iterator.next().done){ console.log(iterator.next().value); }
what?,消除循環變量而已,須要搞這麼麻煩,代碼上不是得不償失了嗎?
並不是如此,畢竟createIterator()只需寫一次,就能夠一直複用。不過ES6
引入了生成器對象,可讓建立迭代器的過程變得更加簡單。spa
生成器是一種返回迭代器的函數,經過function
關鍵字後的星號(*)來表示,函數中會用到新的關鍵字yield
。code
function *createIterator(items) { for(let i=0; i<items.length; i++) { yield items[i]; } } let iterator = createIterator([1, 2, 3]); // 既然生成器返回的是迭代器,天然就能夠調用迭代器的next()方法 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}"
上面,咱們用ES6
的生成器,大大簡化了迭代器的建立過程。咱們給生成器函數createIterator()傳入一個items數組,函數內部,for循環不斷從數組中生成新的元素放入迭代器中,每遇到一個yield
語句循環都會中止;每次調用迭代器的next()方法,循環便繼續運行並中止在下一條yield
語句處。orm
生成器是個函數:對象
function *createIterator(items) { ... }
能夠用函數表達式方式書寫:blog
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]);
在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<count; i++) { yield "repeat"; } } function *createCombinedIterator() { let result = yield *createNumberIterator(); yield *createRepeatingIterator(result); } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: undfined, done: true }"
注意,不管經過何種方式調用迭代器的next()方法,數值3永遠不會被返回,它只存在於生成器createCombinedIterator()的內部。但若是想輸出這個值,則能夠額外添加一條yield語句:
function *createNumberIterator() { yield 1; yield 2; return 3; } function *createRepeatingIterator(count) { for(let i=0; i<count; i++) { yield "repeat"; } } function *createCombinedIterator() { let result = yield *createNumberIterator(); yield result; // 這裏加一句yield yield *createRepeatingIterator(result); } var iterator = createCombinedIterator(); 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: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: "repeat", done: false }" console.log(iterator.next()); // "{ value: undfined, done: true }"