本文主要來深刻剖析ES6的Iterator(迭代器/遍歷器),在瞭解它以前,咱們首先要知道爲何須要Iterator? 它出現的緣由是什麼?javascript
平時開發中,咱們常常會用到循環,拿最基本的來講,好比循環一個數組:html
// for循環 var arr = [1, 2, 3, 4]; for(let i = 0; i < arr.length; i++) { console.log(arr[i]); // 1 2 3 4 } // 也能夠用forEach循環 arr.forEach(item => { console.log(item); // 1 2 3 4 }) // 也能夠用for in循環 for(let i in arr) { console.log(arr[i]); // 1 2 3 4 }
若是是循環輸出字符串的每個字符java
// for循環 var str = 'abcde'; for(var i = 0; i < str.length; i++) { console.log(str[i]); // a b c d e } // for in循環 for(var k in str){ console.log(str[k]); // a b c d e } // forEach不能遍歷字符串,若是硬要用則只能先把字符串轉爲數組再用數組方式循環
若是是循環輸出一個map對象呢, 妥妥的就一個foreach循環,普通的for循環和for...in都不行node
var map = new Map(); map.set('first', '第一'); map.set('second', '第二'); map.set('third', '第三'); // forEach循環 map.forEach((val,key) => { console.log(val, key); // 第一 first // 第二 second // 第三 third });
對於上述各集合數據,咱們發現沒有一個循環方法是能夠一次性解決的。數組
雖然forEach循環不能循環字符串,但字符串能夠轉爲數組再使用forEach便可輸出,但這操做並不舒服每次使用都要轉換。並且forEach循環存在缺點:不能使用break,continue語句跳出循環,或者使用return從函數體返回
。數據結構
而for循環在有些狀況寫代碼會增長複雜度,並且不能循環對象。閉包
相比下,for...in的缺點是不只遍歷數字鍵名,還會遍歷手動添加的自定義鍵,甚至包括原型鏈上的鍵。for...in主要仍是爲遍歷對象而設計的,並不太適用於遍歷數組。函數
以下代碼this
Array.prototype.protoValue = 'hello'; var arr = [1, 2, 3, 4]; arr.test = 'test'; for(let i in arr) { console.log(arr[i]); // 1 2 3 4 test hello }
話說回來,有沒有一種更好的循環能一統上述循環問題? ES6就有了,用for...of循環spa
它的出現解決了什麼問題呢?首先是固然是能解決了上述咱們的問題
對上述數據進行循環輸出
for(let v of arr) { console.log(v); // 1 2 3 4 } for(let v of str) { console.log(v); // a b c d e } for(let v of map) { console.log(v); // (2) ["first", "第一"] // (2) ["second", "第二"] // (2) ["third", "第三"] }
來看看它的優勢:
阮一峯老師的《ECMAScript 6 入門》提到:
ES6中引入了for...of循環,做爲遍歷全部數據結構的統一的方法。Iterator是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。
一個數據結構只要具備 iterator 接口,就能夠用for...of循環遍歷它的成員。
也就是說,並非全部的對象都能使用for...of循環,只有實現了Iterator接口的對象,纔可以for...of來進行遍歷取值。
那麼,Iterator是什麼呢?咱們接着說
它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。
如今明白了,Iterator的產生主要是爲了使用for...of方法。但具體Iterator概念仍是有些抽象,若是要直接具體的描述的話:
Iterator其實就是一個具備 next()方法的對象,而且每次調用next()方法都會返回一個結果對象,這個結果對象有兩個屬性, 以下
{ value: 表示當前的值, done: 表示遍歷是否結束 }
這裏多了一些概念,咱們來梳理一下:
Iterator是一個特殊的對象:
value
和done
。value
表示具體的返回值;done是布爾類型,表示集合是否完成遍歷,沒有則返回true,不然返回false根據上述描述,咱們來模擬一個迭代器,代碼以下
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 } console.log(iterator.next()); // { done: true, value: undefined } console.log(iterator.next()); // { done: true, value: undefined }
過程:
i
的值,每次調用i
都會+1
,指向下一個。故第一次指針指向數據結構的第一個成員,輸出1。看到這裏你們大概知道Iterator
了,但Iterator接口主要供for...of消費。咱們試着用for...of循環上面建立Iterator對象
var iterator = makeIterator([1, 2, 3]); for (let value of iterator) { console.log(value); // Uncaught TypeError: iterator is not iterable }
結果報錯,說明咱們生成的 iterator 對象並非可遍歷的,這樣的結構還不可以被for...of循環
那什麼樣的結構纔是可遍歷的呢?
ES6還引入了一個新的Symbol對象,symbol值是惟一的。
一個數據結構只要部署了Symbol.iterator屬性,就被視爲具備 iterator 接口;調用這個接口,就會返回一個遍歷器對象。這樣的數據結構才能被稱爲可迭代對象(Iterator),該對象可被for...of遍歷。
照着上面的規定來建立一個可迭代對象
var arr = [1, 2, 3]; arr[Symbol.iterator] = function() { var _this = this; var i = 0; return { next: function() { var done = (i >= _this.length); var value = !done ? _this[i++] : undefined; return { done: done, value: value }; } }; } // 此時能夠for...of遍歷 for(var item of arr){ console.log(item); // 1 2 3 }
由此,咱們也能夠知道 for...of 遍歷的實際上是對象的 Symbol.iterator 屬性。
在ES6中,全部的集合對象,包括數組,類數組對象(arguments對象、DOM NodeList 對象),Map和Set,還有字符串都是可迭代的,均可以被for...of遍歷,由於他們都有默認的迭代器。
下面就挑其中幾個類型來舉例子:
var arr = [1, 2, 3]; var iteratorObj = arr[Symbol.iterator](); console.log(iteratorObj.next()); console.log(iteratorObj.next()); console.log(iteratorObj.next()); console.log(iteratorObj.next());
輸出結果:
由上述對迭代器的概念認識,輸出結果也徹底符合咱們預期。
咱們知道普通對象是默認沒有部署這個接口的,因此arguments這個屬性沒有在原型上,而是在對象自身的屬性上。
function test(){ var obj = arguments[Symbol.iterator](); console.log(arguments); console.log(obj.next()); console.log(obj.next()); console.log(obj.next()); } test(1, 2, 3);
輸出結果:
<div class="test">1</div> <div class="test">2</div> <div class="test">3</div>
const nodeList = document.getElementsByClassName('test') for (const node of nodeList) { console.log(node); }
輸出結果:
此次直接從原型上找來證實
console.log(Map.prototype.hasOwnProperty(Symbol.iterator)); // true
至於其餘類型的可迭代對象你們可觸類旁通。
有一些場合會默認調用 Iterator 接口(即Symbol.iterator方法)
let set = new Set().add('a').add('b').add('c'); let [x, y] = set; console.log(x, y); // a b
擴展運算符(...)也會調用默認的 Iterator 接口,能夠將當前迭代對象轉換爲數組。
var str = 'hello'; console.log([...str]); // ["h", "e", "l", "l", "o"] let arr = ['b', 'c']; console.log(['a', ...arr, 'd']); // ["a", "b", "c", "d"]
yield*後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。
let generator = function* () { yield 1; yield* [2,3,4]; yield 5; }; var iterator = generator(); 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: 4, done: false } console.log(iterator.next()); // { value: 5, done: false } console.log(iterator.next()); // { value: undefined, done: true }
關於yield以後會再寫一篇專題
例如:for...of、Set()、Map()、Array.from()等。咱們主要是爲了證實調用上面這些函數時着實是用到了Iterator接口。
咱們貼出上面的實現可迭代對象的代碼,進行改動。想想,儘管咱們沒有手動添加 Symbol.iterator屬性,但由於 ES6 默認部署了 Symbol.iterator,數組仍是能夠遍歷成功,那固然咱們也能夠手動修改這個屬性,從新部署也能夠。
若是咱們手動修改生效,影響了輸出,證實某方法調用須要用到Iterator接口
var arr = [1, 2, 3]; arr[Symbol.iterator] = function() { var _this = this; var i = 0; return { next: function() { var done = (i >= _this.length); var value = !done ? _this[i++] : undefined; return { done: done, value: value + ' 手動添加的屬性' // 添加自定義值 }; } }; } // 輸出結果顯示手動修改生效,證實for...of調用了Iterator接口 for(var item of arr){ console.log(item); // 1 手動添加的屬性 // 2 手動添加的屬性 // 3 手動添加的屬性 } var set = new Set(arr); console.log(set); // Set(3) {"1 手動添加的屬性", "2 手動添加的屬性", "3 手動添加的屬性"}
上述其餘方法證實也是如此,就不一一列出了。
有些數據結構是在現有數據結構的基礎上,計算生成的。好比,ES6 的數組、Set、Map 都部署瞭如下三個方法,調用後都返回遍歷器對象。
var arr = ["first", "second", "third"]; for (let index of arr.keys()) { console.log(index); } // 0 // 1 // 2 for (let color of arr.values()) { console.log(color); } // first // second // third for (let item of arr.entries()) { console.log(item); } // [ 0, "first" ] // [ 1, "second" ] // [ 2, "third" ]
const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function'; console.log(isIterable(new Set())); // true console.log(isIterable(new Map())); // true console.log(isIterable([1, 2, 3])); // true console.log(isIterable("hello world")); // true
梳理了半天,回到最初,Iterator的產生主要是爲了使用for...of方法。而對象不像數組的值是有序的,遍歷的時候根本不知道如何肯定他們的前後順序,因此要注意的是for...of循環不支持遍歷對象。
若是非要遍歷對象,同理對象也必須包含[Symbol.iterator]的屬性並實現迭代器方法,能夠經過手動利用Object.defineProperty方法添加該屬性。
var obj = { a: 2, b: 3 } for (let i of obj) { console.log(i) // Uncaught TypeError: obj is not iterable }
關於Iterator就寫到這裏了,下一篇寫ES6的Generator生成器。
若是該文對你有幫助,點個贊哦,若有錯誤請指正~~~