什麼是偽陣列
能通過Array.prototype.slice轉換為真正的陣列的帶有length屬性的物件。
這種物件有很多,比較特別的是arguments物件,還有像呼叫getElementsByTagName,document.childNodes之類的,它們都返回NodeList物件都屬於偽陣列。
我們可以通過Array.prototype.slice.call(fakeArray)將偽陣列轉變為真正的Array物件。
來看個示例:
1 2 3 4 5 |
var fakeArray01 = {0:'a',1:'b',length:2};//這是一個標準的偽陣列物件 var arr01 = Array.prototype.slice.call(fakeArray01); alert(arr01[0]);//a var arr02 = [].slice.call(fakeArray01); alert(arr02[0]);//a |
slice 可以用來獲取陣列片段,它返回新陣列,不會修改原陣列。
示例中可以看到fakeArray被成功的轉換成了Array物件。也許大家對Array.prototype.slice.call這種寫法比較陌生,其實我們也可以通過[].slice.call這種形式實現同樣的效果,那為什麼我們要通過prototype的形式實現呢,答案是以prototype的形式執行程式效率更高,同樣程式碼也更加優美。
偽陣列的實現
我們來看一些特殊的用例
1 2 3 4 5 6 |
var fakeArray01 = {a:'a',b:'b',length:2};//沒有length下標對應的值 var arr01 = Array.prototype.slice.call(fakeArray01); alert(arr01[0]);//undefined var fakeArray02 = {0:'a',1:'b',length:'num'};//length不是數值 var arr02 = Array.prototype.slice.call(fakeArray02); alert(arr02[1]);//undefined |
同樣fakeArray01和fakeArray02被轉換成了真正的陣列,但是陣列中的值都為undefined
檢視 V8 引擎 array.js 的原始碼,可以將 slice 的內部實現簡化為:
1 2 3 4 5 6 7 |
function slice(start, end) { var len = ToUint32(this.length), result = []; for(var i = start; i < end; i++) { result.push(this[i]); } return result; } |
可以看出,slice 並不需要 this 為 array 型別,只需要有 length 屬性即可。並且 length 屬性可以不為 number 型別,當不能轉換為數值時,ToUnit32(this.length) 返回 0.
根據以上結論可以得出:fakeArray01被轉換成了lenth為2的陣列,其值都被初始化為undefined,fakeArray02被轉換成了length為0的陣列,自然訪問下標為1的元素返回undefined
IE的問題
針對於標準瀏覽器slice實現已經可以解釋所有的問題,但是IE在處理NodeList時出現了問題。IE中無法將NodeList轉換為真正的陣列,會出錯。這又是為什麼呢?嚴格說,在IE內部定義了一個抽象類Arraioid,Array和Arguments都繼承與此,所以可以用slice。但DOM物件是通過COM接入到JScript的,slice檢測的時候失效。
Jquery與偽陣列
Jquery內部大量運用了偽陣列。可以說整個Jquery物件,都是構建在偽陣列的基礎之上的,好讓我們來看一些Jquery的實際運用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>fakeArray</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script src="jquery-1.4.2.js" type="text/javascript"></script> <script> $(document).ready(function(){ var body = $("body"); alert(body.get(0).tagName); }); </script> </head> <body> <div id="test"></div> </body> </html> |
再簡單不過的程式了,好,讓我們來看一下其內部的實現原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
jQuery.fn = jQuery.prototype = { init: function (selector, context) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) if (!selector) { return this; } // Handle $(DOMElement) if (selector.nodeType) { this.context = this[0] = selector; this.length = 1; return this; } // The body element only exists once, optimize finding it if (selector === "body" && !context) { this.context = document; this[0] = document.body; this.selector = "body"; this.length = 1; return this; } //... ... }, get: function (num) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object (num < 0 ? this.slice(num)[0] : this[num]); } } |
最後,我們來解釋一下,程式的執行細節.但是在這之前,還得說一下關於Jquery的內部的一些東西。
用過Jquery的使用者應該都知道$()函式,它是Jquery的選擇器代表。我們可能通過$()函式去選取頁面中的元素(具體語法可引數Jquery幫助文件)。實際上當我們執行$()函式時,程式去執行上面列出的init方法,我們來看一下在呼叫$(document)時所發生的事件:
1 2 3 4 5 6 7 8 9 10 11 |
//$(document) init: function( selector, context ) { var match, elem, ret, doc; // Handle $(DOMElement) : 處理DOM元素, if ( selector.nodeType ) { this.context = this[0] = selector; //給屬性0賦予selector值,此時就是document物件 this.length = 1; //建立偽陣列,更新下標 return this; //返回Jquery物件 } //... ... } |
$(“body”)是同樣的道理,不再多說了。
我們知道Jquery裡所有的操作返回的都是Jquery物件,那我們如何得到其所對應的dom物件呢,Jquery為我們提供了一個get方法,這是專門用來從jquery物件中取得DOM物件用的,由此,便有了body.get(0),那為什麼又是get(0)而不是get()呢,因為Jquery的所有操作都是針對於陣列進行的。所以,在get方法裡,我們要傳一個下標值,來得到具體的元素。現在該看get方法的具體實現了:
1 2 3 4 5 6 7 8 |
get: function( num ) { return num == null ? //如果沒有num,則直接返回DOM陣列 this.toArray() : //如果指定的num,則返回指定下標的元素 //this.slice是jquery的另一個方法,它內部其實還是呼叫Array.prototype.slice來實現將偽陣列轉換為真實的陣列 ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); } |
關於偽陣列就到這吧,我想應該已經差不多了。
注:有機會的話,將來可能會出一個”超越Jquery”系列,專門分析Jquery內部執行細節。但是由於Jquery內部的有各種歪門邪道的手法還不是很理解,所以這是將來的問題了。
符合類陣列物件的兩條規則
- 它們都有一個合法的 length 屬性(0 到 2**32 – 1 之間的正整數)。
- length 屬性的值大於它們的最大索引(index)。
參考連結:
http://lifesinger.org/blog/2010/05/array-prototype-slice/ (已經失效)
http://segmentfault.com/a/1190000002648510