jQuery最令人驚讚的東西就是那個類數組對象,亦即俗話中的jQuery對象。注意,在jQuery類庫中,jQuery是作為命名空間而存在的函數。它擁有許多靜態方法,由於函數也是對象,對象就有原型,而jQuery的原型方法是異常頻繁地調用它的靜態方法。它的第一個原型方法叫init,這是受Prototype類庫影響的結果。init方法也可以作為建構函式使用,它new出來的執行個體就是jQuery對象。在jQuery命名空間的原型中,也有一個叫jquery的屬性,起著其他類庫版本號碼的作用,由於名字特殊,也用作識別是否為jQuery對象的作用。換言之,這裡有三處可以集中添加方法與屬性的地方,jQuery命名空間,用於添加靜態方法,jQuery的原型,用於添加原型方法,只要依賴extend來添加,jQuery.prototype.init執行個體(jQuery對象),它的屬性比較少,只有length,prevObject,selector與context。至於jQuery.prototype.init執行個體的原型,都是往jQuery.prototype裡搬。接著下來,我們沿著jQuery的思路,實現一個類數組對象吧。
首先我們要定義一個類,我稱之為eve,它其實是個工廠。
var eve = function(){ return new eve.prototype.init(arguments); }
如果是數組,傳入一個數字,會返回與數字值相等長度的空數組,如果傳入幾個參數,這幾個參數會成為返回數組的元素,如果什麼都不會就返回空數組(這種方式一定要使用new操作符)。嘛,由於我們的類數組,用不著百分之一百類比,就按其方式二產生類數組。
類數組要求擁有length屬性與索引,length容易辦,但索引我們要一個個賦值,如果在最前面添加新的元素時,就要重排索引,這很要命,效率很低。不過我們可以用數組的push方法實現。
setArray : function(els) { this.length = 0;//設定length以及重排索引 Array.prototype.push.apply(this, els); return this; },
為了確保用得了setArray方法,我們必須保證els為數組,因為裡面用到 Array.prototype.push.apply(this, els),看到Array沒有?至於push方法,簡單,保證調用者擁有length屬性就行了。 下面的makeArray,就是將非數組對象轉換為數組對象,就算為空白,也要用[]包裹起來。
makeArray : function( arr ) {//把傳入參數變成數組 var ret = []; if( arr != null ){ var i = arr.length; //單個元素,但window, string、 function有 'length'的屬性,加其它的判斷 if( i == null || arr.split || arr.setInterval || arr.call ){ ret[0] = arr; }else{ try{ ret = Array.prototype.slice.call(arr) }catch(e){ while( i ) ret[--i] = arr[i];//Clone數組 } } } return ret; },
有了這兩個方法,我們就可以寫我們的構造方法了。
init : function(obj){ this.setArray(this.makeArray(obj)); return this; },
但僅僅是這樣無法new執行個體的,因為這裡的this出了些狀況。前兩個this為eve的執行個體,第三個返回的this為eve.prototype.init的執行個體。
<br /> var eve = function(){<br /> return new eve.prototype.init(arguments);<br /> }<br /> eve.prototype = {<br /> init : function(obj){<br /> this.setArray(this.makeArray(obj));<br /> return this;<br /> },<br /> setArray : function(elems) {<br /> this.length = 0;//設定length以及重排索引<br /> Array.prototype.push.apply(this, elems);<br /> return this;<br /> },<br /> makeArray : function( arr ) {//把傳入參數變成數組<br /> var ret = [];<br /> if( arr != null ){ var i = arr.length;<br /> //單個元素,但window, string、 function有 'length'的屬性,加其它的判斷<br /> if( i == null || arr.split || arr.setInterval || arr.call ){<br /> ret[0] = arr;<br /> }else{<br /> try{<br /> ret = Array.prototype.slice.call(arr)<br /> }catch(e){<br /> while( i ) ret[--i] = arr[i];//Clone數組<br /> }<br /> }<br /> }<br /> return ret;<br /> }<br /> }<br /> try{<br /> var e = new eve(1,2,3);<br /> alert(e);<br /> }catch(err){<br /> alert(err)<br /> }<br />
運行代碼
看到沒有?報錯找不到makeArray方法,換言之eve.prototype.init執行個體無法調用eve的原型方法。如果是jQuery,就是jQuery對象無法調用其jQuery命名空間對象的原型方法。解決方案很簡單,直接把它們兩個的原型重疊起來。為其命名空間對象的原型添加方法就是為其真身的原型添加方法。
eve.prototype.init.prototype = eve.prototype;
<br /> var eve = function(){<br /> return new eve.prototype.init(arguments);<br /> }<br /> eve.prototype = {<br /> init : function(obj){<br /> this.setArray(this.makeArray(obj));<br /> return this;<br /> },<br /> setArray : function(elems) {<br /> this.length = 0;//設定length以及重排索引<br /> Array.prototype.push.apply(this, elems);<br /> return this;<br /> },<br /> makeArray : function( arr ) {//把傳入參數變成數組<br /> var ret = [];<br /> if( arr != null ){ var i = arr.length;<br /> //單個元素,但window, string、 function有 'length'的屬性,加其它的判斷<br /> if( i == null || arr.split || arr.setInterval || arr.call ){<br /> ret[0] = arr;<br /> }else{<br /> try{<br /> ret = Array.prototype.slice.call(arr)<br /> }catch(e){<br /> while( i ) ret[--i] = arr[i];//Clone數組<br /> }<br /> }<br /> }<br /> return ret;<br /> }<br /> }<br /> eve.prototype.init.prototype = eve.prototype;<br /> try{<br /> var e = new eve(1,2,3);<br /> alert(e);<br /> }catch(err){<br /> alert(err)<br /> }<br />
運行代碼
我們再向它添加一些方法,看到底能模擬到什麼程式。其實上面已經能用索引取值了,即e[1],會彈出2。但toString卻是[object Object],而是不數位序列。在jQuery中有個get()方法,如果不加參數,能取出裡面的數組,我們把它的toString方法賦其eve.prototype就是。
toString : function(){//返回一個字串 var array = Array.prototype.slice.call( this ); return array.toString(); }, get: function( num ) { return num === undefined ? Array.prototype.slice.call( this ) : this[ num ]; }
<br /> var eve = function(){<br /> return new eve.prototype.init(arguments);<br /> }<br /> eve.prototype = {<br /> init : function(obj){<br /> this.setArray(this.makeArray(obj));<br /> return this;<br /> },<br /> setArray : function(elems) {<br /> this.length = 0;//設定length以及重排索引<br /> Array.prototype.push.apply(this, elems);<br /> return this;<br /> },<br /> makeArray : function( arr ) {//把傳入參數變成數組<br /> var ret = [];<br /> if( arr != null ){ var i = arr.length;<br /> //單個元素,但window, string、 function有 'length'的屬性,加其它的判斷<br /> if( i == null || arr.split || arr.setInterval || arr.call ){<br /> ret[0] = arr;<br /> }else{<br /> try{<br /> ret = Array.prototype.slice.call(arr)<br /> }catch(e){<br /> while( i ) ret[--i] = arr[i];//Clone數組<br /> }<br /> }<br /> }<br /> return ret;<br /> },<br /> toString : function(){//返回一個字串<br /> var array = Array.prototype.slice.call( this );<br /> return array.toString();<br /> },<br /> valueOf:function(){return Array.prototype.slice.call( this );},<br /> get: function( num ) {<br /> return num === undefined ? Array.prototype.slice.call( this ) : this[ num ];<br /> }<br /> }</p><p> eve.prototype.init.prototype = eve.prototype;<br /> try{<br /> var e = new eve(1,2,3);<br /> alert(e);<br /> alert(e[1]);<br /> alert(e.toString());<br /> alert(e.valueOf());<br /> }catch(err){<br /> alert(err)<br /> }<br />
運行代碼
很好,現在如果不用instanceof與typeof判斷,光憑感覺,是分辯不出它是數組與類數組了。我們再給添加一些數組方法。
shift :[].shift, push: [].push, sort: [].sort, pop: [].pop, splice: [].splice, concat: [].concat, slice: [].slice, constructor:eve,//******************** eve.toString = function(){ return "function Array(){\n [variant code]\n}" }
<br /> var eve = function(){<br /> return new eve.prototype.init(arguments);<br /> }<br /> eve.prototype = {<br /> init : function(obj){<br /> this.setArray(this.makeArray(obj));<br /> return this;<br /> },<br /> isArray : function( obj ) {<br /> return Object.prototype.toString.call(obj) === "[object Array]";<br /> },<br /> setArray : function(elems) {<br /> this.length = 0;//設定length以及重排索引<br /> Array.prototype.push.apply(this, elems);<br /> return this;<br /> },<br /> makeArray : function( arr ) {//把傳入參數變成數組<br /> var ret = [];<br /> if( arr != null ){ var i = arr.length;<br /> //單個元素,但window, string、 function有 'length'的屬性,加其它的判斷<br /> if( i == null || arr.split || arr.setInterval || arr.call ){<br /> ret[0] = arr;<br /> }else{<br /> try{<br /> ret = Array.prototype.slice.call(arr)<br /> }catch(e){<br /> while( i ) ret[--i] = arr[i];//Clone數組<br /> }<br /> }<br /> }<br /> return ret;<br /> },</p><p> inArray : function(elem, array) {<br /> for (var i = 0, length = array.length;i </p><p><button type="button" title="runcode4" class="runcode direct">運行代碼</button></p><p>在jQuery中,它還實現eq方法與index方法來取得元素或元素的索引值,還實現javascript1.6的幾個迭代器,each(forEach),map,filter。這樣高度模擬的類數組對象讓jQuery類庫輕易實現了鏈式操作。但顯然,它的filter與map方法比我們在網上找到的實現複雜多了,因為早在jQuery1.0.1中就搞了pushStack方法,用於儲存執行push,pop等破壞性操作前的數組對象。</p><p>可能有人對init方法感覺非常不爽,因為它的兩種this導致了需要用到兩個原型。那是jQuery的init除了有產生jQuery對象的能力,還能充當domReady用。如果是單純的類數組,我們完全可以去掉它,但產生類數組對象時,我們就一定要用new操作符了。</p><pre class="brush:javascript;gutter:false;toolbar:true;collapse:true"> var eve = function(){ this.setArray(this.makeArray(arguments)); return this; } eve.prototype = { isArray : function( obj ) { return Object.prototype.toString.call(obj) === "[object Array]"; }, setArray : function(elems) { this.length = 0;//設定length以及重排索引 Array.prototype.push.apply(this, elems); return this; }, makeArray : function( arr ) {//把傳入參數變成數組 var ret = []; if( arr != null ){ var i = arr.length; //單個元素,但window, string、 function有 'length'的屬性,加其它的判斷 if( i == null || arr.split || arr.setInterval || arr.call ){ ret[0] = arr; }else{ try{ ret = Array.prototype.slice.call(arr) }catch(e){ while( i ) ret[--i] = arr[i];//Clone數組 } } } return ret; }, inArray : function(elem, array) { for (var i = 0, length = array.length;i </pre><p>