關於JavaScript繼承,方式很多,包括compile-to-javascript的語言TypeScript, CoffeeScript以及網站MDN, GitHub, Modernizr各種polyfill都給出了穩妥的實現方案。
從ES5的角度看,這其中一些方案在功能上OK,但在語義上卻不盡如人意。
本人從這些方案中採取一些比較潮的思路,整理出一份方案,可實現與原生DOM類繼承的風格一致,達到功能和語義兼得的效果(當然,就別再老想着99後ES3了)。
如果你的WebApp是基於ES5運行環境的,可以評估或fork此方案。
//exports Function.prototype.extends
//exports global.getPrototypeNames
//基於ES5的繼承實現函數
Function.prototype.extends=(function(){
function getOwnPropertyDescriptors(object){
return Object.getOwnPropertyNames(object).reduce(function(pds,pn){
return pds[pn]=Object.getOwnPropertyDescriptor(object,pn),pds;
},{});
}
/**
* 繼承一個類。
* 若有後續類,則共享後續類截至到當前的快照屬性(constructor除外),
* 這些屬性中的getters,setters和methods須考慮到要是通用的(如Array的那些methods)
**/
function inherits(s){
var c,p;
c=this;
if(typeof s==="function"){
p=Object.create(s.prototype,getOwnPropertyDescriptors(c.prototype));
}else{
p=c.prototype;
}
if(arguments.length>1){
Array.prototype.slice.call(arguments,1).forEach(function(s){
var pds=getOwnPropertyDescriptors(s.prototype);
delete pds.constructor;
Object.defineProperties(p,pds);
});
}
c.prototype=p;
}
return inherits;
}());
//測試準備
//~~~~~~~~~~~~~~~~~~~~~~~~
// BaseList extends Array
//~~~~~~~~~~~~~~~~~~~~~~~~
function BaseList(){
this.length=this.length;
}
BaseList.prototype.add=function(e){
return this.push(e);
};
BaseList.extends(Array);
//~~~~~~~~~~~~~~~~~~~~~~~~
// ItemList extends BaseList
//~~~~~~~~~~~~~~~~~~~~~~~~
function ItemList(){
BaseList.call(this);
}
ItemList.extends(BaseList,EventTarget);
ItemList.prototype.item=function item(index){
index>>>=0;
return index<this.length?this[index]:null;
};
//~~~~~~~~~~~~~~~~~~~~~~~~
// ElementList extends ItemList
//~~~~~~~~~~~~~~~~~~~~~~~~
function ElementList(){
ItemList.call(this);
}
ElementList.extends(ItemList);
ElementList.prototype.namedItem=function namedItem(name){
var index=this.findIndex(function(elem){return elem.name===name;});
return index===-1?null:this[index];
};
//測試工具函數之獲取原型鏈名單
var getPrototypeNames=(function(){
function typeOf(value){
return Object.prototype.toString.call(value).slice(8,-1);
}
function isObject(value){
return typeof value==="object"&&value!==null||typeof value==="function"
}
function constructorNameOf(proto){
return typeof proto.constructor==="function"?proto.constructor.name:typeOf(proto)
}
function getPrototypeNames(object){
var names,proto;
names=[];
proto=Object.getPrototypeOf(object);
while(isObject(proto)){
names.push(constructorNameOf(proto));
proto=Object.getPrototypeOf(proto)
}
return names;
}
return getPrototypeNames;
}());
//執行測試
var list=new ElementList();
console.dir(list);
console.log("list: "+getPrototypeNames(list).join(" > "));
list.push(document.documentElement);
list.push(document.head);
console.assert(list.item(1)===document.head,"The second item of list is document.head");