Javascript - Arraylike的7種實現


jQuery的崛起讓ArrayLike(類數組)在javascript中大放異彩,它的出現為一組數據的行為(函數)擴展提供了基礎。

類數組和數組相似,具有數組的某些行為,但是它相比數組可以更加自由的擴展,它的存在讓一組數據的表現不再受限於數組,也無需去污染數組本身的原型——它來自javascript對象的挖掘和擴展,而並非javascript本身就存在的。簡單的說,它來自數組,比數組更加適合擴展。

本文原創於linkFly原文地址

這篇文章主要分為以下知識

鋒芒畢露的ArrayLike

如果你已經了解了ArrayLike,這一節可以略過。

ArrayLike(類數組/偽數組)即擁有數組的一部分行為,在DOM中早已表現出來,而jQuery的崛起讓ArrayLike在javascript中大放異彩。正如它的翻譯一樣:它類似於數組。

ArrayLike對象的精妙在於它和javascript原生的Array類似,但是它是自由構建的,它來自開發者對javascript對象的擴展,也就是說:對於它的原型(prototype)我們可以自由定義,而不會污染到javascript原生的Array。

過去針對一組數據的擴展是下面這個樣子的:

        //污染Array實現擴展
        Array.prototype.demo = function () {
            //check
        };
        var test = [];
        test.demo();

上面代碼你們懂的,污染了Array,在協同式開發中這簡直就是作孽啊——ArrayLike應此誕生。

ArrayLike讓你對一組數據的擴展不再受限於Array本身,同時也不會影響到Array,說白了就是:一組數據,肯定是有數組來存,但是如果要對這組數據進行擴展,會影響到數組原型,ArrayLike的出現則提供了一個中間數據橋梁,ArrayLike有數組的特性, 但是對ArrayLike的擴展並不會影響到原生的數組。舉個栗子:

爸爸媽媽對你期望很高,你要好好學習,但是舍友基佬教會了你打dota,整天拉你打dota讓你沒時間看書學習,結果呢,就是打得一手好dota學習掉下去了——但是如果,你開了分身斧,讓你的分身去打dota,你自己仍然好好學習,dota學習兩不誤,而且你的分身不僅僅可以打dota,也可以去打wow,把妹,做你做不到的事情,是不是覺得這樣不就碉堡了么!!!

沒錯,ArrayLike就是要干這么碉堡的事情。

常見的ArrayLike有下面這幾個,詳見:其他

  • Arguments
  • NodeList
  • StyleSheetList
  • HTMLCollection
  • HTMLFormControlsCollection (繼承HTMLCollection)
  • HTMLOptionsCollection(繼承HTMLCollection)
  • HTMLAllCollection
  • DOMTokenList

ArrayLike的實現

第一種 - 通過閉包實現:

通過閉包實現,內部采用一個Array作為基礎,API是針對數組進行操作,在API的實現上較差。並且不支持直接通過索引(array[0])來訪問元素,通過閉包實現上會丟失instanceof的判定,優點是夠輕。

    !function () {
        //通過閉包實現
        var List = function () {
            var list = [],
                self = {
                    constructor: List,
                    //如果希望更像原生一點,將length定義為屬性,那么length則需要自己維護
                    length: function () { return list.length; },
                    add: function (item) {
                        list.push(item);
                    },
                    eq: function (index) {
                        return list[index];
                    }
                };
            return self;
        };
        //測試
        console.group('第一種 - 通過閉包實現');
        var demo = new List();
        demo.add('List - add()');
        console.log('demo instanceof List : %c' + (demo instanceof List), 'color:red;');
        console.log('demo.constructor === List :%c' + (demo.constructor === List), 'color:blue');
        //無法通過索引demo[0]這種方式訪問
        console.log('成員:[ ' + demo.eq(0) + ' , ' + demo.eq(1) + ' ]');
        console.log('length:' + demo.length());
        //注意看demo對象
        console.log(demo);
        console.groupEnd();
    }();

運行結果和demo對象結構:

第二種 - 通過繼承實現:

主要亮點(應用)在保留Array的API,在Array上二次封裝,可以通過索引來訪問。

    !function () {
        //通過繼承數組實現,數組原生方法會被繼承過來
        var List = function () { };
        List.prototype = [];
        List.prototype.constructor = List;
        List.prototype.add = function (item) {
            this.push(item);
        };
        //測試
        console.group('第二種 - 通過繼承實現');
        var demo = new List();
        //源於繼承
        demo.push('Array - push()');
        demo.add('List - add()');
        console.log('demo instanceof List : %c' + (demo instanceof List), 'color:blue;');
        console.log('demo.constructor === List :%c' + (demo.constructor === List), 'color:blue');
        console.log('[ ' + demo[0] + ' , ' + demo[1] + ' ]');
        console.log('length:' + demo.length);
        //注意看demo對象
        console.log(demo);
        console.groupEnd();
    }();

運行結果和demo對象結構:

第三種 - 通過自我維護實現:

在增刪改上需要自我維護length,相比下來很是折騰和繁瑣,只是提供一種代碼思路,並不提倡,可以通過索引訪問,

    !function () {
        //通過自動維護length實現
        var List = function () {
            this.length = 0;
        };
        List.prototype.add = function (item) {
            //讓對象模擬Array的行為
            this[this.length++] = item;
        };
        console.group('第三種 - 通過自我維護實現');
        var demo = new List();
        demo.add('List - add()');
        console.log('demo instanceof List : %c' + (demo instanceof List), 'color:blue');
        console.log('demo.constructor === List :%c' + (demo.constructor === List), 'color:blue');
        console.log('[ ' + demo[0] + ' , ' + demo[1] + ' ]');
        console.log('length:' + demo.length);
        //注意看demo對象
        console.log(demo);
        console.groupEnd();
    }();

運行結果和demo對象結構:

第四種 - 針對第一種優化:

在add中通過Array原生的APIArray.prototype.push來實現,原理是只要調用過Array原生的增刪改API操作函數(僅第一次即可),則可以通過索引來訪問元素,但是instanceof的判定仍未修復。

    !function () {
        //第四種Array-Like
        var List = function () {
            var self = {
                constructor: List,
                length: 0,
                add: function (item) {
                    //本質在這里,交給Array的自動維護
                    [].push.call(this, item);
                }
            };
            return self;
        };
        console.group('第四種 - 針對第一種優化');
        var demo = new List();
        demo.add('List - add()');
        console.log('demo instanceof List : %c' + (demo instanceof List), 'color:red;');
        console.log('demo.constructor === List :%c' + (demo.constructor === List), 'color:blue');
        console.log('[ ' + demo[0] + ' , ' + demo[1] + ' ]');
        console.log('length:' + demo.length);
        console.log(demo);
        console.groupEnd();
    }();

運行結果和demo對象結構:

第五種 - 修復instenceof判定:

這種修復有點勉強,因為在ie下並沒有__proto__,所以這里所謂的修復只不過是針對現代瀏覽器而已,只是提供一種思路,關於instenceof請參考請參考:其他

    !function () {
        //第五種,我們看見上面那種instanceOf並不能返回正確的結果,於是我們修正它
        var List = function () {
            /*
            instanceof 檢測一個對象A是不是另一個對象B的實例的原理是:
            查看對象B的prototype指向的對象是否在對象A的[[prototype]]鏈上。
            如果在,則返回true,如果不在則返回false。
            不過有一個特殊的情況,當對象B的prototype為null將會報錯(類似於空指針異常)。
            reference:http://kb.cnblogs.com/page/77478/
            */
            self = {
                constructor: List,
                length: 0,
                //強制引用__proto__,IE並不支持
                __proto__: List.prototype,
                add: function (item) {
                    push.call(this, item);
                }
            },
            //cache
            push = Array.prototype.push;
            return self;
        };
        console.group('第五種 - 修復instenceOf判定');
        var demo = new List();
        demo.add('List - add()');
        console.log('demo instanceof List : %c' + (demo instanceof List), 'color:blue;'); 
        console.log('demo.constructor === List :%c' + (demo.constructor === List), 'color:blue');
        console.log('[ ' + demo[0] + ' , ' + demo[1] + ' ]');
        console.log('length:' + demo.length);
        console.log(demo);
        console.groupEnd();
    }();

運行結果和demo對象結構:

第六種 - jQuery的實現:

jQuery構造函數繁瑣的實現不僅僅只是為了去new化,同時也修復了針對jQuery對象的判定,巧妙的將原型重新指向,讓instenceof可以在原型鏈中查找到jQuery構造函數,使得instenceOf判定有效,讓jQuery直逼真正的javascript對象。

    !function () {
         //jQuery Array-Like實現
        var jQuery = function () {
            return new jQuery.fn.init();
        }, push = Array.prototype.push;
        jQuery.fn = jQuery.prototype = {
            constructor: jQuery,
            length: 0,
            add: function (item) {
                //使用Array.prototype.push添加元素,會自動維護length
                push.call(this, item);
            }
        };
        jQuery.fn.init = function () {
            return this;
        };
        //漂亮的重置prototype
        jQuery.fn.init.prototype = jQuery.fn;
        console.group('第六種 - jQuery的實現');
        var demo = new jQuery();
        demo.add('List - add()');
        console.log('demo instanceof jQuery : %c' + (demo instanceof jQuery), 'color:blue');
        console.log('demo.constructor === jQuery : %c' + (demo.constructor === jQuery), 'color:blue');
        console.log('[ ' + demo[0] + ' , ' + demo[1] + ' ]');
        console.log('length:' + demo.length);
        console.log(demo);
        console.groupEnd();
    }();

運行結果和demo對象結構:

第七種 - 最簡單的實現:

並沒有采用閉包,而是通過定義原型實現,實現方法類似第四種,但是原型指向正確,instenceof判定有效。

    //最簡單的類數組實現
    !function () {
        var List = function () { }, push = Array.prototype.push;
        List.prototype = {
            constructor: List,
            length: 0,
            add: function (item) {
                push.call(this, item);
            }
        };
        console.group('第七種 - 最簡單的實現');
        var demo = new List();//只是需要new
        demo.add('List - add()');
        console.log('demo instanceof List : %c' + (demo instanceof List), 'color:blue;'); 
        console.log('demo.constructor === List :%c' + (demo.constructor === List), 'color:blue');
        console.log('[ ' + demo[0] + ' , ' + demo[1] + ' ]');
        console.log('length:' + demo.length);
        console.log(demo);
        console.groupEnd();
    }();

運行結果和demo對象結構:

第八種 - jQuery拆解版:

為了更好的理解jQuery的構造函數實現,所以給出了這種,jQuery.fn.init就是本例中的ArrayLike對象,jQuery只是把init掛載到jQuery.prototype上了而已。

    (function () {
        var List = function () {
            return new ArrayLike();
        }, ArrayLike = function () {//這個array-like就是jQuery拆解版的實現
        };
        List.prototype = {
            constructor: List,
            length: 0,
            add: function (item) {
                Array.prototype.push.call(this, item);
            }
        };
        //就是jQuery的jQuery.fn.init.prototype = jQuery.fn;
        ArrayLike.prototype = List.prototype;
        //測試
        console.group('第八種 - jQuery拆解版');
        var demo = List(); //這樣就不用new了
        demo.add('List - add()');
        console.log('demo instanceof List : %c' + (demo instanceof List), 'color:blue;'); 
        console.log('demo.constructor === List :%c' + (demo.constructor === List), 'color:blue');
        console.log('[ ' + demo[0] + ' , ' + demo[1] + ' ]');
        console.log('length:' + demo.length);
        console.log(demo);
        console.groupEnd();
    })();

運行結果和demo對象結構:

其實應該叫做類數組對象的7次實現...有點標題黨的意思.....不要打臉...

其他

作者:linkFly
聲明:嘿!你都拷走上面那么一大段了,我覺得你應該也不介意順便拷走這一小段,希望你能夠在每一次的引用中都保留這一段聲明,尊重作者的辛勤勞動成果,本文與博客園共享。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM