想要學好一種語言,一定要了解其語言特性,因爲我們 not just coding,js本身就是一種函數式的編程語言,它不與java或者其他面向對象一樣擁有繼承機制,但是我們可以利用其留下的prototype庫進行曲線救國,這就不能不找一段經典的繼承代碼來研究一下了。以下是John Resig實現的經典繼承實現代碼,
// 自執行的匿名函數創建一個上下文,避免引入全局變量
(function() {
// initializing變量用來標示當前是否處於類的創建階段,
var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
// 基類構造函數
// 這裏的this是window,所以這整段代碼就向外界開闢了一扇窗戶 - window.Class
this.Class = function() { };
// 繼承方法定義
Class.extend = function(prop) {
var _super = this.prototype;
// 通過將子類的原型指向父類的一個實例對象來完成繼承
// - 注意:this是基類構造函數(即是Class)
initializing = true;
var prototype = new this();
initializing = false;
for (var name in prop) {
prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" ?
(function(name, fn) {
return function() {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
return ret;
};
})(name, prop[name]) :
prop[name];
}
function Class() {
// 在類的實例化時,調用原型方法init
if (!initializing && this.init)
this.init.apply(this, arguments);
}
// 子類的prototype指向父類的實例(完成繼承的關鍵)
Class.prototype = prototype;
// 修正constructor指向錯誤
Class.constructor = Class;
// 子類自動獲取extend方法,arguments.callee指向當前正在執行的函數
Class.extend = arguments.callee;
return Class;
};
})();
var Person = Class.extend({
name:null,
init:function(){
alert("init person");
},
alerting:function(){
alert("Person");
}
})
var Student = Person.extend({
init:function(){
this._super();
alert("init");
},
alerting:function(){
this._super();
alert("Student");
}
})
var p = new Student();
p.alerting();
首先通過創建一個自執行函數避免引入全局變量,主要來分析一下Class.extend這個方法,該方法中首先將父類(也就是Class)這個類的原型prototype屬性賦給_super這個變量,initializing主要是用於控制父類實例化時,prototype = new this()時,防止進入if條件,也就是這段代碼,
if (!initializing && this.init)
this.init.apply(this, arguments);
是否執行接下來聲明一個prototype的變量,指向父類的一個實例,prototype = new this(),這句代碼其實是將prototype的__proto__屬性指向了父類的原型,接下來將initializing這個變量=false,是爲了志明自執行方法中的return的Class這個類在後期實例化時進入if條件,例如Person就可以。相信大家都注意到了for循環中的代碼,這就是繼承機制的核心,看這個條件語句 typeof prop[name] == "function" && typeof _super[name] == "function" ,是說明如果prop對象中的name屬性是一個function並且父類中存在同名的屬性並且同樣是function,那麼執行下面這段代碼:
(function(name, fn) {
return function() {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
return ret;
};
})(name, prop[name])
這段代碼是什麼意思呢,這裏面其實做了兩件事情,首先將父類的同名方法賦給了this綁定的_super屬性,然後就是將子類的方法綁定到this作用域執行,我們回過頭來看看,這段代碼是賦給了prototype[name],也就是子類的該同名方法,該方法必定是由定義該方法類的實例調用,因此this作用域指向了目標類的實例,此時,this作用域中還有一個_super屬性,該屬性此時需要注意的是該屬性不是不變的,舉例來說,var p = new Student()這個實例,當實例p調用init()函數時,this._super指向了Person的init(),當p調用alerting()時,this._super指向了Person的alerting(),當我們調用p.alerting()的時候,執行this._super()其實就是調用父類的同名方法,若typeof prop[name] == "function" && typeof _super[name] == "function"不爲true的時候,那麼prototype[name] = proto[name],這其中還有一點點不好理解,如上代碼我改動一點點,將Person類中的alerting方法變成一個屬性,那麼這個條件也不成立,就執行prototype[‘alerting’]=proto['alerting'],這就有問題了,之前我們在prototype=new this()的時候,prototype的__proto__屬性指向了父類的prototype,說明其中必定有父類中的alerting屬性,這下prototype又有了一個alerting屬性,那麼我們的對象p在調用p.alerting()的時候調用的到底是alerting方法還是alerting屬性呢,其實還是alerting()方法,因爲首先對象p會在第一個__proto__屬性中找alerting()方法,若找不到,則在下一層中尋找,若找到了該方法,就不會進入下一層的__proto__屬性中繼續尋找了,所以這裏不會找到從父類繼承下來的alerting屬性,調用的只是自己的方法。到此爲止,其實只是實現了將父類的方法屬性繼承到了我們自定義的局部變量prototype屬性中,後面我們定義的Class方法,該方法不同於之前的Class方法,
function Class() {
// 在類的實例化時,調用原型方法init
if (!initializing && this.init)
this.init.apply(this, arguments);
}
我們將Class的prototype指向了我們定義的prototype變量,其實就是繼承了父類的方法和屬性,此時Class.constructor指向了prototype變量的構造函數,所以我們顯式地改回來,Class.constructor=Class,並且使用Class.extend=arguments.callee將Class.extend方法指向了外層的Class.extend方法,方便我們進行深度擴展,最後將Class返回。
這個意思就是無論是Person或者是Student類,本質上都是Class.extend方法內部定義的Class方法(也就是類),只是我們將父類(外層的Class,也就是調用extend方法的類)和子類(也就是prop對象)的屬性揉到了內部的Class類中,最後將Class返回給我們外部,就實現了繼承了。
以上就是該繼承機制的詳解,不知道講清楚了沒有,若有疑問或者有不對之處,敬請指正。