瞭解ES6中的Iterator迭代器
寫在前面
迭代器是帶有特殊介面的物件,用以為不同的資料結構提供統一的訪問機制
由於目前ES6版本有4種可遍歷的“集合”(Array,Object,Map,Set,其中三種自帶預設迭代器),所以擁有Iterator之後一切都變的簡單了許多。我們只需要為其部署Iterator介面即可遍歷
遍歷過程
- 建立一個指標物件,指向當前資料結構的起始位置
- 第一次呼叫只針對象的next方法,將指標指向資料結構的第一個成員
- 。。。。
- 直至資料結構的結束位置
每次呼叫next方法都會返回資料結構的當前成員資訊,即一個包含value和done兩個屬性的物件。value為對應成員值,而done則表示遍歷是否結束
在網上找了一段ES5模擬Interator的程式碼:
function createIterator(ary) { var i = 0; return { next: function() { return { value: ary[i++], done: i > ary.length } } } } var iterator = createIterator(['a', 'b', 'c']) var done = false; while (!done) { var result = iterator.next(); console.log(result); done = result.done; } //{ value: 'a', done: false } //{ value: 'b', done: false } //{ value: 'c', done: false } //{ value: undefined, done: true }
Ps:這裡只是模擬了Array資料型別的遍歷,真正的Iterator會有一個型別識別的(小聲BB)
有沒有讓大家想起當年學習C語言時候的連結串列?用指標把一串地址串聯起來,依次訪問下一個元素,這也是迭代器的本質
可迭代型別
由於ES6種引入了新的Symbol物件(唯一性),定義了一個Symbol.iterator屬性,所以只要物件中含有這個屬性那麼都是可迭代的。之所以前邊提到的四種集合均可迭代,就是因為他們都有預設的迭代器
讓我們舉一個栗子:
let ary = ['a', 'b', 'c']; let Iterator = ary[Symbol.iterator](); console.log(iterator.next()); //{ value: 'a', done: false } console.log(iterator.next()); //{ value: 'b', done: false } console.log(iterator.next()); //{ value: 'c', done: false } console.log(iterator.next()); //{ value: undefined, done: true }
如圖,我們將陣列物件的預設iterator的值賦給了let出的新的Iterator值,並且通過next不斷向後取值。不過這個是ES6自帶的迭代器有些老鐵看了可能不服氣。那咱們自己搞一個不自帶迭代器的型別:Object
const obj = {
b: 2
}
const a = 'a'
obj.a = 1;
Object.defineProperty(obj, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function () {
const that = this; //儲存指標
let index = 0;
const ks = Object.keys(that);
return {
next: function() {
return {
value: that[ks[index++]],
done: (index > ks.length)
}
}
}
}
})
for(const v of obj) {
console.log(v); // 2 , 1
}
遍歷之時我們要做的就是通過Object.keys去除他的key值,然後通過next向後查詢。由於它被設定了[Symbol.iterator]屬性,所以for…of可以找到並呼叫
對於類似於陣列的物件,即存在數值鍵名以及length屬性,部署Iterator介面有一個更加簡便的方法:使用Symbol.iterator方法直接引用陣列的Iterator介面
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
上面兩種方法都可以實現。NodeList物件是類似陣列的物件,本來就具有遍歷介面,可以直接遍歷。上圖我們將其便利介面改成陣列的Symbol.iterator屬性不會有任何影響
事實上,Symbol.iterator屬性對應一個函式,執行後返回當前物件的遍歷器物件。本質上還是通過指標結構的遍歷。我們最後再把它的指標結構模擬一下
function Obj(value){
this.value = value;
this.next = next;
}
Obj.prototype[Symbol.iterator] = function(){
var iterator = { next: next };
var current = this;
function next(){
if(current){
var value = current.value;
current = current.next;
return {done: false, value: value};
}else{
return {done: true};
}
}
return iterator
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for(var i of one){
console.log(i); ///1,2,,3
}
上邊程式碼我們首先在原型鏈上部署Symbol.iterator方法,呼叫該方法會返回遍歷器物件iterator,呼叫該物件的next方法,在返回一個值得同時自動將內部指標移到下一個例項
Import Points
經典用法。
當資料集合非常大的時候,使用iterator可以幫助我們省去很多麻煩。
const fibonacci = {
[Symbol.iterator]: function () {
let [pre, next] = [0, 1];
return {
next() {
[pre, next] = [next, pre + next];
return {
value: next,
done: next > 1000
}
}
}
}
}
for(var n of fibonacci) {
console.log(n)
}
// 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,987
Ps:[Symbol.iterator]是一個預定義好的,Symbol型別的特殊值
此值為value和done組成的鍵值對
我們舉一個阮一峰老師的栗子
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() //{value:'a', done:false}
iter.next() //{value:'b', done:false}
iter.next() //{value:'c', done:false}
iter.next() //{value:undefined, done:true}
擴充套件運算子(…)
擴充套件運算子(…)也會呼叫預設的Iterator介面
var str = 'hello';
[...str] //['h', 'e', 'l', 'l', 'o']
let arr = ['b', 'c'];
['a', ...arr, 'd'] //['a', 'b', 'c', 'd']
實際上這提供了一種簡便機制,可以將任何部署了Iterator介面的資料結構轉化為陣列
eg:let arr = […iterable];
yield*
yield*後面跟的是一個可遍歷結構,亦會呼叫該結構的遍歷器介面
let generator = function* (){
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next(); //{value: 1, donne:false};
iterator.next(); //{value: 2, donne:false};
iterator.next(); //{value: 3, donne:false};
iterator.next(); //{value: 4, donne:false};
iterator.next(); //{value: 5, donne:false};
iterator.next(); //{value: undefined, donne:true}
最後說兩句
Symbol.iterator方法的最簡單實現還是使用今後要介紹道的Generator函式,通過yield命令給出每一步的返回值,從而實現一個生成器。具體的話我們下一篇部落格再詳細說說吧