由於javascript原生是不支持類的(ES6已經支持class與extends),更不用談繼承、多態了,為了模擬出一些其它面向對象編程語言的這些特性,有好多大牛寫了給出了實現方式,看了John Resig的《Simple JavaScript Inheritance》這篇文章,深深被折服了,原來短短幾十行javascript也可以這麼強大、優雅,下面以我的理解方式來解讀下。
主要實現了繼承、訪問父類的重名方法(這裡的實現方式太妙了),但遺憾的是不能實現成員變量/函數的隱藏。
(function(){
//設置標志位,是new A()過程中還是 B=A.extends({/* */})過程中;
var initializing = false,
//fnTest 可取結果為倆正則對象 /\b_super\b/與 /.*/
//當正則的test方法參數支持自動調用toString()方法時取前面那個
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// 創建一個全局的 Class對象
this.Class = function(){};
// 創建繼承函數
Class.extend = function(prop) {
//此處把_super指向父類的prototype,屬性繼承時需要用其判斷父、子是否有同名方法
var _super = this.prototype;
//開始B=A.extends({/* */});
initializing = true;
//實例化父類,並把父類的實例方法及屬性給prototype
var prototype = new this();
initializing = false;
//結束B=A.extends({/* */});
// 遍歷用戶傳入的用於構建子類的對象
//處理的地方包括:
//1、屬性直接存到prototype上
//2、方法中沒通過this._super()調用父類中的同名方法,則直接把該方法存到prototype上
//3、方法中有通過this._super()調用父類中的同名方法,則把如下過程包裹成一個函數存到prototype上:
// 把this._super指向父類的同名方法,然後再調用子類的該方法並返回執行結果
for (var name in prop) {
// 循環體中看起來略微復雜,是整個代碼的精華所在
//簡化下此過程就是 v = a && b && c ? d : e; 等同於 v = (a && b && c) ? d : e
//該過程中主要運用的就是邏輯運算中的短路運算 a,b,c全為true則v=d,否則v=e
//a中判斷prop[name]是否是函數,b中判斷父類中是否也有同名的name函數,c主要判斷name函數中是否有調用父類的同名方法(即調用了this._super())
//d就是a,b,c同時滿足的時候,也就是說:name是函數,且name函數存在與父類中,且子類的name函數需要調用了父類的同名函數
//若a,b,c中有一項不滿足則直接把prop[name]給prototype[name]
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
//首先,先決條件決定了prototype[name]是個函數,所以先包裹一個函數返回
return function() {
//作者這裡備份,然後調用fn後又還原this._super
//由於return的是一個function,具有延時調用的作用 所以在子類調用fn時this始終指向子類本身
//而這裡的_super是父類的_super,與this._super並無關系,所以備份應該是多余的
//var tmp = this._super;
//this._super指向與fn(即prop[name])同名的父類方法,方便fn內部調用
//這裡是實現子類中通過this._super()調用父類同名函數的關鍵
this._super = _super[name];
//此時再調用子類,子類裡面的this._super已經指向了父類同名的函數,即_super[name]
var ret = fn.apply(this, arguments);
//this._super = tmp;
//返回執行結果
return ret;
};
})(name, prop[name]) :
prop[name];
}
//此“Class”與外頭那個“Class”是兩個東西
//這個Class實為子類的構造函數
function Class() {
//不是A.extends({/* */})過程中
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
//前面處理好的prototype綁定給子類的prototype
Class.prototype = prototype;
//修正構造函數
Class.prototype.constructor = Class;
//賦予子類可被繼續繼承的功能
Class.extend = arguments.callee;
return Class;
};
})();
如果想更深入了解js繼承,請繼續往下查看文章