1. 程式人生 > >JavaScript iterator 設計模式

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處理一般物件時一般使用兩種方法,一種是將普通物件轉化為迭代器物件,另一種就是上面這種寫法。

設計模式週週講