一段標準的 for 循環代碼:git
var colors = ["red", "green", "blue"]; for (var i = 0, len = colors.length; i < len; i++) { console.log(colors[i]); }
看着很簡單,可是再回顧這段代碼,實際上咱們僅僅是須要數組中元素的值,可是卻須要提早獲取數組長度,聲明索引變量等,尤爲當多個循環嵌套的時候,更須要使用多個索引變量,代碼的複雜度就會大大增長,好比咱們使用雙重循環進行去重:es6
function unique(array) { var res = []; for (var i = 0, arrayLen = array.length; i < arrayLen; i++) { for (var j = 0, resLen = res.length; j < resLen; j++) { if (array[i] === res[j]) { break; } } if (j === resLen) { res.push(array[i]); } } return res; }
爲了消除這種複雜度以及減小循環中的錯誤(好比錯誤使用其餘循環中的變量),ES6 提供了迭代器和 for of 循環共同解決這個問題。github
所謂迭代器,其實就是一個具備 next() 方法的對象,每次調用 next() 都會返回一個結果對象,該結果對象有兩個屬性,value 表示當前的值,done 表示遍歷是否結束。數組
咱們直接用 ES5 的語法建立一個迭代器:瀏覽器
function createIterator(items) { var i = 0; return { next: function() { var done = i >= item.length; var value = !done ? items[i++] : undefined; return { done: done, value: value }; } }; } // iterator 就是一個迭代器對象 var iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // { done: false, value: 1 } console.log(iterator.next()); // { done: false, value: 2 } console.log(iterator.next()); // { done: false, value: 3 } console.log(iterator.next()); // { done: true, value: undefined }
除了迭代器以外,咱們還須要一個能夠遍歷迭代器對象的方式,ES6 提供了 for of 語句,咱們直接用 for of 遍歷一下咱們上節生成的遍歷器對象試試:babel
var iterator = createIterator([1, 2, 3]); for (let value of iterator) { console.log(value); }
結果報錯 TypeError: iterator is not iterable
,代表咱們生成的 iterator 對象並非 iterable(可遍歷的)。數據結構
那什麼纔是可遍歷的呢?異步
其實一種數據結構只要部署了 Iterator 接口,咱們就稱這種數據結構是「可遍歷的」(iterable)。函數
ES6 規定,默認的 Iterator 接口部署在數據結構的 Symbol.iterator 屬性,或者說,一個數據結構只要具備 Symbol.iterator 屬性,就能夠認爲是"可遍歷的"(iterable)。code
舉個例子:
const obj = { value: 1 }; for (value of obj) { console.log(value); } // TypeError: iterator is not iterable
咱們直接 for of 遍歷一個對象,會報錯,然而若是咱們給該對象添加 Symbol.iterator 屬性:
const obj = { value: 1 }; obj[Symbol.iterator] = function() { return createIterator([1, 2, 3]); }; for (value of obj) { console.log(value); } // 1 // 2 // 3
由此,咱們也能夠發現 for of 遍歷的實際上是對象的 Symbol.iterator 屬性。
然而若是咱們直接遍歷一個數組對象:
const colors = ["red", "green", "blue"]; for (let color of colors) { console.log(color); } // red // green // blue
儘管咱們沒有手動添加 Symbol.iterator 屬性,仍是能夠遍歷成功,這是由於 ES6 默認部署了 Symbol.iterator 屬性,固然咱們也能夠手動修改這個屬性:
var colors = ["red", "green", "blue"]; colors[Symbol.iterator] = function() { return createIterator([1, 2, 3]); }; for (let color of colors) { console.log(color); } // 1 // 2 // 3
除了數組以外,還有一些數據結構默認部署了 Symbol.iterator 屬性。
因此 for...of 循環可使用的範圍包括:
其實模擬實現 for of 也比較簡單,基本就是經過 Symbol.iterator 屬性獲取迭代器對象,而後使用 while 遍歷一下:
function forOf(obj, cb) { let iterable, result; if (typeof obj[Symbol.iterator] !== "function") throw new TypeError(result + " is not iterable"); if (typeof cb !== "function") throw new TypeError("cb must be callable"); iterable = obj[Symbol.iterator](); result = iterable.next(); while (!result.done) { cb(result.value); result = iterable.next(); } }
爲了更好的訪問對象中的內容,好比有的時候咱們僅須要數組中的值,但有的時候不只須要使用值還須要使用索引,ES6 爲數組、Map、Set 集合內建瞭如下三種迭代器:
以數組爲例:
var colors = ["red", "green", "blue"]; for (let index of colors.keys()) { console.log(index); } // 0 // 1 // 2 for (let color of colors.values()) { console.log(color); } // red // green // blue for (let item of colors.entries()) { console.log(item); } // [ 0, "red" ] // [ 1, "green" ] // [ 2, "blue" ]
Map 類型與數組相似,可是對於 Set 類型須要注意如下:
var colors = new Set(["red", "green", "blue"]); for (let index of colors.keys()) { console.log(index); } // red // green // blue for (let color of colors.values()) { console.log(color); } // red // green // blue for (let item of colors.entries()) { console.log(item); } // [ "red", "red" ] // [ "green", "green" ] // [ "blue", "blue" ]
Set 類型的 keys() 和 values() 返回的是相同的迭代器,這也意味着在 Set 這種數據結構中鍵名與鍵值相同。
並且每一個集合類型都有一個默認的迭代器,在 for-of 循環中,若是沒有顯式指定則使用默認的迭代器。數組和 Set 集合的默認迭代器是 values() 方法,Map 集合的默認迭代器是 entries() 方法。
這也就是爲何直接 for of 遍歷 Set 和 Map 數據結構,會有不一樣的數據結構返回:
const values = new Set([1, 2, 3]); for (let value of values) { console.log(value); } // 1 // 2 // 3
const values = new Map([["key1", "value1"], ["key2", "value2"]]); for (let value of values) { console.log(value); } // ["key1", "value1"] // ["key2", "value2"]
遍歷 Map 數據結構的時候能夠順便結合解構賦值:
const valuess = new Map([["key1", "value1"], ["key2", "value2"]]); for (let [key, value] of valuess) { console.log(key + ":" + value); } // key1:value1 // key2:value2
咱們能夠在 Babel 的 Try it out 中查看編譯的結果:
const colors = new Set(["red", "green", "blue"]); for (let color of colors) { console.log(color); }
對於這樣一段代碼,編譯的結果以下:
"use strict"; var colors = new Set(["red", "green", "blue"]); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for ( var _iterator = colors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true ) { var color = _step.value; console.log(color); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } }
至少由編譯的結果能夠看出,使用 for of
循環的背後,仍是會使用 Symbol.iterator 接口。
而這段編譯的代碼稍微複雜的地方有兩段,一段是 for 循環這裏:
for ( var _iterator = colors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true ) { var color = _step.value; console.log(color); }
跟標準的 for 循環寫法有些差異,咱們看下 for 語句的語法:
for (initialize; test; increment) statement;
initialize、test 和 increment 三個表達式之間用分號分割,它們分別負責初始化操做
、循環條件判斷
和計數器變量的更新
。
for 語句其實就至關於:
initialize; while (test) { statement; increment; }
代碼的邏輯爲:先進行初始化,而後每次循環執行以前會執行 test 表達式,並判斷表達式的結果來決定是否執行循環體,若是 test 計算結果爲真值,則執行循環體中的 statement。最後,執行 increment 表達式。
並且值得注意的是,其實 for 循環中的三個表達式中任意一個均可以被忽略,不過度號仍是要寫的。
好比 for(;;)
,不過這就是一個死循環……
好比:
var i = 0, len = colors.length; for (; i < len; i++) { console.log(colors[i]); }
又好比:
var i = 0, len = colors.length; for (; i < len; ) { i++; }
而後咱們再來看 Babel 編譯的這個 for 循環表達式:
for ( var _iterator = colors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true ) { var color = _step.value; console.log(color); }
用 while 的寫法至關於:
var _iterator = colors[Symbol.iterator](), _step; while (!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) { var color = _step.value; console.log(color); _iteratorNormalCompletion = true; }
是否是就好懂了不少呢,而後你就會發現,其實 _iteratorNormalCompletion = true
這句是徹底沒有必要的……
另一段稍微複雜的代碼是:
try { ... } catch (err) { ... } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { ... } }
由於 _iteratorNormalCompletion = (_step = _iterator.next()).done
,因此 _iteratorNormalCompletion 表示的就是是否完成了一次完整的迭代過程,若是沒有正常的迭代完成,而且迭代器有 return 方法時,就會執行該方法。
而之因此這麼作,就要提到迭代器的 return 方法。
引用阮一峯老師的 ECMAScript 6 入門:
遍歷器對象除了具備 next 方法,還能夠具備 return 方法和 throw 方法。若是你本身寫遍歷器對象生成函數,那麼 next 方法是必須部署的,return 方法和 throw 方法是否部署是可選的。return 方法的使用場合是,若是 for...of 循環提早退出(一般是由於出錯,或者有 break 語句或 continue 語句),就會調用 return 方法。若是一個對象在完成遍歷前,須要清理或釋放資源,就能夠部署 return 方法。
咱們能夠舉個例子:
function createIterator(items) { var i = 0; return { next: function() { var done = i >= items.length; var value = !done ? items[i++] : undefined; return { done: done, value: value }; }, return: function() { console.log("執行了 return 方法"); return { value: 23333, done: true }; } }; } var colors = ["red", "green", "blue"]; var iterator = createIterator([1, 2, 3]); colors[Symbol.iterator] = function() { return iterator; }; for (let color of colors) { if (color == 1) break; console.log(color); } // 執行了 return 方法
不過正如你在編譯後的代碼中看到,僅僅是在有 return 函數的時候執行了 return 函數而已,return 函數中返回的值其實並不生效……
可是你不返回值或者返回一個基本類型的值的話,結果又會報錯……
TypeError: Iterator result undefined is not an object
這是由於 return 方法必須返回一個對象,而這又是 Generator 規範決定的……
總之若是是在瀏覽器中使用的話,return 函數的返回值其實並不生效 T^T
ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級做用域、標籤模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。