JavaScript iterator 設計模式
迭代器模式就是按照順序訪問一個物件中元素,而不用暴露該物件的內部組成。迭代器模式就是將這個迭代實現從業務中分離出來。
但實際開發中我們並不將他當成一個設計模式。
前瞻後顧
說起迭代器,想必對ES6有了解的同學應該不會陌生。我們知道,for ... of
遍歷的物件必須是迭代器物件,而普通物件則不能,因為普通物件內部沒有實現迭代器,而像陣列則內部實現了迭代器,所以可以用for ... of
的語法,而對於一般物件在ES5中有專門的處理方法,for ... in
和
Object.keys()
,而 for ... in
可遍歷所有的的物件,但是它遍歷特殊物件,如陣列,也會遍歷它的length,這並不是我們需要的,有時還會出現不按順序的遍歷。
在我們日常使用中一般是將普通物件轉化為特殊物件然後處理的。
仿jQuery迭代器
這裡我只簡單的實現陣列的遍歷,至於如何迭代普通物件,我們下面再做介紹。
var $ = {
each: function (arr, fn) {
for (var i = 0, len = arr.length; i < len; i++) {
fn.call(arr[i], i, arr[i])
}
}
};
$.each([1, 2, 3, 4, 5, 6], function(i, val) {
console.log([i, val]);
});
迭代器的分類
迭代器根據實現的位置,我們將它分為內部迭代器和外部迭代器兩種。
內部迭代器
內部迭代器對於使用者來說他不用關心迭代器的內部實現,只用關注使用的效果,我們上面仿jQuery的each就是個內部迭代器的實現。
內部迭代器有它的好處但是也有它的不足,比如我們要比較兩個陣列是否相等,上面的方法就不滿足我們的需要,我們就需要寫一個新的方法來實現。
var $ = {
each: function (arr, fn) {
for (var i = 0, len = arr.length; i < len; i++) {
fn.call(arr[i], i, arr[i])
}
}
};
var compareArray = function(arr, arr2) {
if( arr.length !== arr2.length) {
return false;
}
$.each(arr, function(i, val) {
if( val !== arr2[i]) {
return false;
}
});
return true;
};
compareArray([1, 2], [1, 2, 3]); // false
外部迭代器
外部迭代器必須顯式地請求才會迭代下一個元素。
外部迭代器雖然增加了使用上的一些麻煩,但是它的靈活性卻正是我們需要的。我們可以人為的控制迭代的過程和順序。
// 迭代器實現
var Iterator = function(obj) {
var current = 0;
var next = function() {
current += 1;
};
var isDone = function() {
return current >= obj.length;
};
var getCurrItem = function() {
return obj[current];
};
var len = function() {
return obj.length;
}
return {
next: next,
isDone: isDone,
getCurrItem: getCurrItem,
length: len,
}
};
// 比較陣列
var compareArray = function (iteratorObj, iteratorObj2) {
if(iteratorObj.length !== iteratorObj2.length) {
return false;
}
while (!iteratorObj.isDone() && !iteratorObj2.isDone()){
if (iteratorObj.getCurrItem() !== iteratorObj2.getCurrItem()){
return false;
}
iteratorObj.next();
iteratorObj2.next();
}
return true;
};
var arr = Iterator([1, 2, 3]);
var arr2 = Iterator([1, 2, 3]);
compareArray(arr, arr2); // true
這樣我們就用ES5實現了迭代器的功能,ES6的實現迭代器相對簡單,如果不熟悉的可以參考一下阮一峰老師的 ES6 使用手冊
迭代物件
使用ES6迭代器後發現,for ... of
能夠遍歷的迭代器物件,如: 陣列,類陣列,Set,Map,arguments等物件它們有一個共同的特性,就是它們都有一個length陣列,可以實現對物件用下標進行訪問。
因此,要實現對普通物件的的迭代,我們可以參考jQuery的實現如下做:
var isArraylike = function(obj) {
return Object.prototype.toString.call(obj) === [object Array];
}
$ = {
each: function(obj, fn) {
var isArray = isArraylike(obj); // 判斷物件是否為陣列
if (isArray) {
for (var i = 0, len = obj.length ; i < len; i++ ) {
if (fn.call(obj[i], i, obj[i]) === false) {
break;
}
}
} else {
for (i in obj) {
if (fn.call(obj[i], i, obj[i]) === false) {
break;
}
}
}
}
};
我們再使用ES6處理一般物件時一般使用兩種方法,一種是將普通物件轉化為迭代器物件,另一種就是上面這種寫法。