在Javascript中,一切皆是對象,所謂的類也只是用來模擬其它面嚮對象語言的class的對象而已,例如:
function MyClass() {}//定義一個類 MyClass.call();//其實這個類本身也是一個對象
在上面的代碼中,並沒有爲MyClass定義call方法,但是卻可以調用call方法,其原因就是JavaScript的原生對象在發揮作用。通過function關鍵字定義一個類,實質上就是實例化一個Function的實例,因爲Javascript在Function的原生對象中定義了call方法,所以即使MyClass的定義是空的,但是還是可以調用call方法!由此,我們就可以看到繼承的影子!
在原生對象中定義的方法call被繼承到了MyClass中,那麼只要修改一下原生對象,那麼就可以模擬出面向對象的繼承機制了。
function MyBaseClass() { this.Add = function(a, b) { return a + b; } } function MyClass() { } MyClass.prototype = new MyBaseClass(); var obj = new MyClass(); var X = obj.Add(3, 4);
只要把MyClass的原生對象指向基類MyBaseClass的實例對象,那麼MyClass就可以繼承MyBaseClass中的方法,從而實現了繼承機制!
多繼承
任何對象的直接原生對象都只有一個,那麼如果想實現多繼承該怎麼辦呢?可以採用兩種辦法:
方法一:鏈狀繼承
function MyBaseClassA() { this.Add = function(a, b) { return a + b; } } function MyBaseClassB() { this.Sub = function(a, b) { return a - b; } } function MyClass() { } MyBaseClassB.prototype = new MyBaseClassA(); MyClass.prototype = new MyBaseClassB(); var obj = new MyClass(); var X = obj.Add(3, 4); var Y = obj.Sub(9, 4);
方法二:拷貝繼承
function MyBaseClassA() { this.Add = function(a, b) { return a + b; } } function MyBaseClassB() { this.Sub = function(a, b) { return a - b; } } function MyClass() { } var A = new MyBaseClassA(); for (var method in A) { MyClass.prototype[method] = A[method]; } var B = new MyBaseClassB(); for (var method in B) { MyClass.prototype[method] = B[method]; } var obj = new MyClass(); var X = obj.Add(3, 4); var Y = obj.Sub(9, 4);
繼承的本質就是屬性拷貝,所以只要手動拷貝基類的所有屬性,那麼就可以實現多繼承了,對於拷貝屬性的代碼可以封裝一下,便於調用:
var Extend = function(DeriveClass,BaseClass) { var o = new BaseClass(); for (var method in o) { DeriveClass.prototype[method] = o[method]; } } Extend(MyClass, MyBaseClassA); Extend(MyClass, MyBaseClassB);
繼承是實現了,但是如果有構造參數有又該怎麼處理呢?答案就是在派生類中調用基類構造就可以了:
function MyBaseClass(a, b) { this.A = a; this.B = b; this.Add = function() { return this.A + this.B; } } function MyClass(a,b,c) { MyBaseClass.call(this, a, b);//調用基類的構造 } var Extend = function(DeriveClass, BaseClass) { var o = new BaseClass(); for (var method in o) { DeriveClass.prototype[method] = o[method]; } } Extend(MyClass, MyBaseClass); var obj = new MyClass(4,5,8); var X = obj.Add(); alert(X); //結果:9
call和apply方法是經常用到的兩個方法,其作用就是在另一個域內調用方法!
繼承機制帶來的另一個問題就是同名方法隱藏問題!Javascript對象都是由原型對象生成,原型對象也有其自己的原型對象,這樣就構成了一條原型對象鏈,對象鏈的頂端指向Object原生對象,末端就是對象本身,如果對象鏈中存在同名的方法,那麼對象鏈末端的方法訪問優先級高於頂端的優先級,因此派生類的方法總是隱藏基類的方法:
var BaseClass=function(){ this.Hello=function(){ return "A"; } this.say=function(){ return "Saying A"; } } var BaseObj = new BaseClass(); var X = BaseObj.Hello(); alert(X); //結果:A BaseClass.prototype.Hello=function(a,b){ return "B"; } var Y = BaseObj.Hello(); alert(Y); //結果:A var MyClass = function(){ this.Hello=function(){ return "C"; } } var Extend = function(DeriveClass, BaseClass) { var o = new BaseClass(); for (var method in o) { DeriveClass.prototype[method] = o[method]; } } Extend(MyClass, BaseClass); var MyObj = new MyClass(); var Z = MyObj.Hello(); alert(Z); //結果:C MyClass.prototype.Hello=function(){ return "D"; } MyClass.prototype.say=function(){ return "Saying D"; } var K = MyObj.Hello(); alert(K); //結果:C var L = MyObj.say(); alert(L); //結果:Saying D
方法隱藏的唯一準則就是原型對象鏈的順序,方法是否會被隱藏與實現繼承的機制相關!
以後再說一說Javascript面向對象的多態特性!