首先建立一個父類的執行個體化對象,然後將該對象賦給子類的 prototype 屬性。
這樣,父類中的所有公有執行個體成員都會被子類繼承。並且用 instanceof 運算子判斷時,子類的執行個體化對象既屬於子類,也屬於父類。
然後將子類本身賦值給它的 prototype 的 constructor 屬性。(注意:這裡賦值的時候是沒有 () 的!)
這一步是為了保證在查看子類的執行個體化對象的 constructor 屬性時,看到的是子類的定義,而不是其父類的定義。
接下來,通過對 o.method1() 調用的結果我們會看到,子類繼承來的公有執行個體方法中,如果調用了私人執行個體欄位或者私人執行個體方法,則所調用的這些私人執行個體成員是屬於父類的。
同樣,通過對 o.method2() 調用的結果我們看到,子類中定義的執行個體方法,如果調用了私人執行個體欄位或者私人執行個體方法,則所調用的這些私人執行個體成員是屬於子類的。
通過對 o.method() 調用的結果我們看到,定義在父類原型上的方法,會被子類繼承。
通過對 o.method3() 調用的結果我們看到,子類中定義的執行個體方法是不能訪問父類中定義的私人執行個體成員的。
最後,通過對 subClass.staticMethod() 調用的結果我們看到,靜態成員是不會被繼承的。
2.4 調用繼承法
調用繼承的本質是,在子類的構造器中,讓父類的構造器方法在子類的執行內容上執行,父類構造器方法上所有通過 this 方式操作的內容實際上都都是操作的子類的執行個體化對象上的內容。因此,這種做法僅僅為了減少重複代碼的編寫。 複製代碼 代碼如下:function parentClass() {
// private field
var x = "I'm a parentClass field!";
// private method
function method1() {
alert(x);
alert("I'm a parentClass method!");
}
// public field
this.x = "I'm a parentClass object field!";
// public method
this.method1 = function() {
alert(x);
alert(this.x);
method1();
}
}
parentClass.prototype.method = function () {
alert("I'm a parentClass prototype method!");
}
parentClass.staticMethod = function () {
alert("I'm a parentClass static method!");
}
function subClass() {
// inherit
parentClass.call(this);
// private field
var x = "I'm a subClass field!";
// private method
function method2() {
alert(x);
alert("I'm a subClass method!");
}
// public field
this.x = "I'm a subClass object field!";
// public method
this.method2 = function() {
alert(x);
alert(this.x);
method2();
}
this.method3 = function() {
method1();
}
}
// test
var o = new subClass();
alert(o instanceof parentClass); // false
alert(o instanceof subClass); // true
alert(o.constructor); // function subClass() {...}
o.method1(); // I'm a parentClass field!
// I'm a subClass object field!
// I'm a parentClass field!
// I'm a parentClass method!
o.method2(); // I'm a subClass field!
// I'm a subClass object field!
// I'm a subClass field!
// I'm a subClass method!
o.method(); // Error!!!
o.method3(); // Error!!!
subClass.staticMethod(); // Error!!!
上面這個例子很好的反映出了如何利用調用繼承法來實現繼承。
利用調用繼承的關鍵只有一步操作:
就是在子類定義時,通過父類的 call 方法,將子類的 this 指標傳入。使父類方法在子類上下文中執行。
這樣,父類中的所有在父類內部通過 this 方式定義的公有執行個體成員都會被子類繼承。
用 instanceof 運算子判斷時,子類的執行個體化對象只屬於子類,不屬於父類。
查看子類的執行個體化對象的 constructor 屬性時,看到的是子類的定義,不是其父類的定義。
接下來,通過對 o.method1() 和 o.method2() 調用的結果跟原型繼承法的調用結果是相同的,所說明的問題也是一樣的,這裡不再重複。
通過對 o.method() 調用的結果我們看到,定義在父類原型上的方法,不會被子類繼承。
通過對 o.method3() 調用的結果我們看到,子類中定義的執行個體方法同樣不能訪問父類中定義的私人執行個體成員的。
最後,通過對 subClass.staticMethod() 調用的結果我們看到,靜態成員同樣不會被繼承的。
最後,還有一點,在這個例子中沒有體現出來,就是通過調用繼承法,可以實現多繼承。也就是說,一個子類可以從多個父類中繼承通過 this 方式定義在父類內部的所有公有執行個體成員。
作為一種弱類型語言,javascript 提供了豐富的多態性,javascript 的多態性是其它強型別物件導向語言所不能比的。
多態
重載和覆蓋
先來說明一下重載和覆蓋的區別。重載的英文是 overload,覆蓋的英文是 override。發現網上大多數人把 override 當成了重載,這個是不對的。重載和覆蓋是有區別的。
重載的意思是,同一個名字的函數(注意這裡包括函數)或方法可以有多個實現,他們依靠參數的類型和(或)參數的個數來區分識別。
而覆蓋的意思是,子類中可以定義與父類中同名,並且參數類型和個數也相同的方法,這些方法的定義後,在子類的執行個體化對象中,父類中繼承的這些同名方法將被隱藏。
重載
javascript 中函數的參數是沒有類型的,並且參數個數也是任意的,例如,儘管你可以定義一個: 複製代碼 代碼如下:function add(a, b) {
return a + b;
}
這樣的函數,但是你仍然可以再調用它是帶入任意多個參數,當然,參數類型也是任意的。至於是否出錯,那是這個函數中所執行的內容來決定的,javascript 並不根據你指定的參數個數和參數類型來判斷你調用的是哪個函數。
因此,要定義重載方法,就不能像強型別語言中那樣做了。但是你仍然可以實現重載。就是通過函數的 arguments 屬性。例如: 複製代碼 代碼如下:function add() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
這樣你就實現了任意多個參數加法函數的重載了。
當然,你還可以在函數中通過 instanceof 或者 constructor 來判斷每個參數的類型,來決定後面執行什麼操作,實現更為複雜的函數或方法重載。總之,javascript 的重載,是在函數中由使用者自己通過操作 arguments 這個屬性來實現的。
覆蓋
實現覆蓋也很容易,例如: 複製代碼 代碼如下:function parentClass() {
this.method = function() {
alert("parentClass method");
}
}
function subClass() {
this.method = function() {
alert("subClass method");
}
}
subClass.prototype = new parentClass();
subClass.prototype.constructor = subClass;
var o = new subClass();
o.method();
這樣,子類中定義的 method 就覆蓋了從父類中繼承來的 method 方法了。
你可能會說,這樣子覆蓋是不錯,但 java 中,覆蓋的方法裡面可以調用被覆蓋的方法(父類的方法),在這裡怎麼實現呢?也很容易,而且比 java 中還要靈活,java 中限制,你只能在覆蓋被覆蓋方法的方法中才能使用 super 來調用次被覆蓋的方法。我們不但可以實現這點,而且還可以讓子類中所有的方法中都可以調用父類中被覆蓋的方法。看下面的例子: 複製代碼 代碼如下:function parentClass() {
this.method = function() {
alert("parentClass method");
}
}
function subClass() {
var method = this.method;
this.method = function() {
method.call(this);
alert("subClass method");
}
}
subClass.prototype = new parentClass();
subClass.prototype.constructor = subClass;
var o = new subClass();
o.method();
你會發現,原來這麼簡單,只要在定義覆蓋方法前,定義一個私人變數,然後把父類中定義的將要被覆蓋的方法賦給它,然後我們就可以在後面繼續調用它了,而且這個是這個方法是私人的,對於子類的對象是不可見的。這樣跟其它進階語言實現的覆蓋就一致了。
最後需要注意,我們在覆蓋方法中調用這個方法時,需要用 call 方法來改變執行內容為 this(雖然在這個例子中沒有必要),如果直接調用這個方法,執行內容就會變成全域對象了。