迭代器模式
迭代器模式是指提供一種方法順序訪問一個聚合物件中的各個元素,而不需要暴露該物件的內部表示。
迭代器分為內部迭代器和外部迭代器。內部迭代器只需一次初始呼叫,而外部迭代器必須顯式地請求迭代下一個元素,這樣我們就可以手動控制迭代過程。
實現一個內部迭代器:
Array.prototype.innerIterator = function(callback){
for (let i = 0, len = this.length; i < len; i++) {
callback && callback.call(this[i], this[i], i)
}
};
[1,2,3].innerIterator(function(item, index){
console.log('item:', item, 'index:', index)
})
// item: 1 index: 0
// item: 2 index: 1
// item: 3 index: 2
複製程式碼
實現一個外部迭代器:
Array.prototype.outerInterator = function(){
let index = 0;
return {
next: () => {
return index < this.length ?
{value: this[index++], done: false}:
{value: undefined, done: true}
}
}
}
let iterator = [1,2,3].outerInterator();
for(let next; (next = iterator.next()) && !next.done;) {
console.log('item', next.value)
}
// item 1
// item 2
// item 3
複製程式碼
迭代協議
瞭解了迭代器模式,再來看看 ES6 中補充的迭代協議。可迭代(iterable)協議和迭代器(iterator)協議。
可迭代協議:
一個可迭代物件(或其原型上),必須有一個 Symbol.iterator
的屬性,該屬性所對應的值為返回一個物件的無參函式,被返回物件符合迭代器協議。當可迭代物件需要迭代時,呼叫該方法。
一些資料型別內建了 @@iterator
方法,有自己預設的迭代行為。(String, Array, TypedArray, Map , Set 等都是內建可迭代物件, 因為它們的原型物件都有一個 @@iterator
方法.)([Symbol.iterator]
、@@iterator
可以認為是一回事)
let iterator = ('hi')[Symbol.iterator]()
var a = iterator.next();
// a { value: 'h', done: false }
複製程式碼
迭代器協議:
一個迭代器必須實現了 next()
方法,該方法是返回一個物件的無參函式。被返回的物件有兩個必要的屬性:done 和 value。
Array.prototype.Iteration = function(){
let index = 0;
return {
[Symbol.iterator](){return this},
next: () => {
return index < this.length ?
{value: this[index++], done: false}:
{value: undefined, done: true}
}
}
};
let Iteration = [2, 3, 4].Iteration();
for(let value of Iteration) {
console.log('value', value)
}
// value 2
// value 3
// value 4
複製程式碼
不能發現,Iteration 同時滿足可迭代協議和迭代協議。又因為是可迭代的,for...of
是可以直接使用,而且這個和外部迭代器十分相似。
一旦一種資料結構有了 @@iterator
方法後, 就認為是可迭代的。ES6 中許多新的方法就是基於此的 解構賦值
、擴充套件運算子
、yield*
,還有 for..of
、Array.from()
等。
知道了以上知識,也就知道了為什麼物件不可以直接使用 for...of
了。不過我們可以在物件原型上新增 @@iterator
方法,使之成為可迭代的。
Object.prototype.Iteration = function(){
let keys = Object.keys(this), index = 0;
return{
[Symbol.iterator](){return this},
next: () => {
let current = index++;
return current < keys.length?
{value: [keys[current], this[keys[current]]], done: false}:
{value: undefined, done: true};
}
}
}
let iterator = {'a': 1, 'b': 2, 'c': 3}.Iteration();
for(let [key, value] of iterator) {
console.log('key:', key, 'value:', value)
}
// key: a value: 1
// key: b value: 2
// key: c value: 3
複製程式碼
生成器
像以上的的物件都是我們自己手動實現的,符合可迭代協議和迭代協議的物件。看起來很麻煩,還好這些工作已經有函式替我們做了,那就是生成器函式。
生成器函式是可以作為迭代器工廠的函式,當它被執行時會返回一個新的 Generator 物件,該物件符合可迭代協議和迭代器協議。
現在我們用生成器函式使得物件符合迭代協議:
Object.prototype.Iteration = function *(){
for(let [key, value] of Object.entries(this)){
yield [key, value]
}
}
for(let [key, value] of {'a': 1, 'b': 2, 'c': 3}.Iteration()) {
console.log('key:', key, 'value:', value)
}
// key: a value: 1
// key: b value: 2
// key: c value: 3
複製程式碼
在這裡生成器函式只是作為生產迭代器的工廠而已,其實它還是訊息雙向傳遞系統。也正是這些特性的存在,使得非同步流程控制又向前邁了一大步。