類陣列和陣列相似,具有陣列的某些行為,但是它相比陣列可以更加自由的擴充套件,它的存在讓一組資料的表現不再受限於陣列,也無需去汙染陣列本身的原型——它來自javascript物件的挖掘和擴充套件,而並非javascript本身就存在的。簡單的說,它來自陣列,比陣列更加適合擴充套件。
這篇文章主要分為以下知識
- 鋒芒畢露的ArrayLike
- ArrayLike的實現
- 其他
鋒芒畢露的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次實現…有點標題黨的意思…..不要打臉…
其他
- 完整原始碼:https://github.com/linkFly6/linkfly.so/blob/master/LinkFLy/LinkFly/ArrayLike.js
- 參考文章:JavaScript類陣列物件參考
- 參考文章:理解instanceof實現原理
來自:linkFly
連結:http://www.cnblogs.com/silin6/p/4309925.html