一、為什么使用Iterator
我們知道,在ES6中新增了很多的特性,包括Map,Set等新的數據結構,算上數組和對象已經有四種數據集合了,就像數組可以使用forEach,對象可以使用for...in.. 進行遍歷一樣,是不是隨着Map和Set的出現也應該給這兩種集合一個遍歷方法呢?如果這樣的話js的方法對象就太多了,既然這四種集合都是需要遍歷的,那么完全可以用一個統一的訪問機制。於是乎Iterator應運而生。
二、Iterator是什么
Iterator是一個訪問機制,同時是一個接口,可以實現在不同的數據集合中,完成它的功能。
Iterator本質是一個指針對象,其中包含一個next方法,這個方法可以改變指針的指向,並且返回一個包含value和done的對象,value為當前所指向的成員的值,done表示是否遍歷完成。通過這些描述,我們大致可以手寫一個Iterator,用於遍歷數組:
// 定義遍歷器對象 let Iterator = function (arr) { let index = 0; // 當前指針 return { next() { return index < arr.length ? {value: arr[index++],done: false} : {value: undefined, done: true}; } } }; let arr = [1, 2, 3]; let it = Iterator(arr); console.log(it.next()); // { value: 1, done: false } console.log(it.next()); // { value: 2, done: false } console.log(it.next()); // { value: 3, done: false } console.log(it.next()); // { value: undefined, done: true }
三、ES6中的Iterator接口的實現
我們知道Iterator的接口就是提供了一個統一的訪問機制,如果我們像上面那樣,不斷的調用next()才能遍歷完成,如果Iterator像java那樣提供一個hasNext()方法的話,那么我們可以通過while進行遍歷,事實上js中是沒有的。之所以沒有是因為ES6使用for...of...實現了對具有Symbol.iterator(可遍歷)的數據結構的遍歷,也就是說只要是包含Symbol.iterator屬性的結構都可以使用for...of...進行遍歷。接下來我們看看我們直接輸出上面四種結構的Symbol.iterator屬性是什么樣子。
let a = [1,2,3]; let it_arr = a[Symbol.iterator](); it_arr.next(); // { value: 1, done: false } it_arr.next(); // { value: 2, done: false } it_arr.next(); // { value: 3, done: false } it_arr.next(); // { value: undefined, done: true } let b = new Set(["a","b","c"]); let it_set = b[Symbol.iterator](); it_set.next(); // { value: 1, done: false } it_set.next(); // { value: 2, done: false } it_set.next(); // { value: 3, done: false } it_set.next(); // { value: undefined, done: true } let c = new Map([["a","1"]]); let it_map =c[Symbol.iterator](); it_map.next(); // { value: [ 'a', '1' ], done: false } it_map.next(); // { value: undefined, done: true } let d = new Object(); d.name = "Jyy"; console.log(d[Symbol.iterator]()); // TypeError: d[Symbol.iterator] is not a function
上面是ES6中在四種數據集合中的Iterator的實現。可以看到ES6並沒有在對象中原生部署Symbol.iterator屬性,因此我們需要手動在對象中設置遍歷器屬性。下面就是一個簡單的在一個對象中設置Symbol.iterator屬性的例子。
function Demo(list){ this.list = list; this[Symbol.iterator] = function(){ let index = 0; return { next: function(){ return index < list.length ? {value: list[index++], done: false} : {value: undefined, done: true}; } } } } let demo = new Demo([1,2,3,4]); for(let i of demo){ console.log(i); // 1 2 3 4 }
上面的代碼就是手動在Demo對象中添加了一個遍歷器對象,在next中實現遍歷的邏輯,然后就可以使用for...of...進行遍歷。當然我們也可以將遍歷器對象部署在Demo的原型對象,for...of...依然會自動調用。
另外,對於類數組的對象(key值為數字索引,包含lenth屬性),我們可以直接將數組的遍歷器對象賦值給對象,代碼如下:
let obj = { 0 : "a", 1 : "b", 2 : "c", length :3, } obj[Symbol.iterator] = Array.prototype[Symbol.iterator]; for(let i of obj){ console.log(i); // a b c }
除此之外,String,函數的arguments對象都原生部署了遍歷器接口
// 字符串可用for...of...進行遍歷 let str = "123"; for(let s of str){ console.log(s); // 1 2 3 } // 函數中的arguments對象可以使用for...of...進行遍歷 function demo(){ for(let i of arguments){ console.log(i); // 1,2,3 } } demo(1,2,3)
四、使用Iterator接口的場合
除了for...of...,es6中還有哪些語法用到了iterator接口呢?
1. 解構賦值
function Demo(list){ this.list = list; this[Symbol.iterator] = function(){ let index = 0; return { next: function(){ index++; return index < list.length ? {value: list[index++], done: false} : {value: undefined, done: true}; } } } } [a,...b] = new Demo([1,2,3,4]); console.log(b); // [2,3,4]
如上代碼中每次返回的value都是1,我們在中括號(注意不是花括號)中使用結構賦值,發現b的值為[2,3,4]
2.擴展運算符
與解構賦值相反的擴展運算符,也使用了遍歷器。
function Demo(list){ this.list = list; this[Symbol.iterator] = function(){ let index = 0; return { next: function(){ return index < list.length ? {value: list[index++], done: false} : {value: undefined, done: true}; } } } } let demo = new Demo([1,2,3,4]); console.log([0, ...demo, 5]); //[ 0, 1, 2, 3, 4, 5 ]
五、與其他遍歷語法的比較
在js中的遍歷語法有:for循環,for...in..., forEach
1. for循環的缺點我們都知道,就是書寫起來太麻煩,如果有快捷鍵的方式的話還好,如果沒有,就要寫很多的代碼,而且看起來也不爽。
2. forEach書寫起來比較簡單,但是它的問題是無法中途跳出循環,break、return、continue等都不能起作用
3. for...in...是專門為遍歷對象而設計的,如果使用這種方法遍歷數組,key則會變為字符串,如下:
let arr = [1,2,3]; for(let a in arr){ console.log(typeof a); // string }
另外我們可能在開發中遇到的問題就是,使用for...in...的話,那么會遍歷原型鏈上的鍵
let person = { age :25 } person.prototype={ name : "jyy" } for(let i in Person){ console.log(i); // age prototype }
