身為一個物件導向的程式開發人員,應該不會不知道繼承 (inheritance) 是什麼吧,它可以讓子類別擁有父類別的完整功能,並透過 private/protected/internal 等修飾子 (modifier) 做封裝的保護,子類別也可以存取父類別的資源,子類別也可以選擇允許或不允許給其他物件繼承等等,若是想要在不修改原本物件的情況下擴充原有功能,繼承是一個好方法。
身為一個物件導向的程式開發人員,應該不會不知道繼承 (inheritance) 是什麼吧,它可以讓子類別擁有父類別的完整功能,並透過 private/protected/internal 等修飾子 (modifier) 做封裝的保護,子類別也可以存取父類別的資源,子類別也可以選擇允許或不允許給其他物件繼承等等,若是想要在不修改原本物件的情況下擴充原有功能,繼承是一個好方法。
例如,現在我手上有一個 MathBase 物件 (Mathbase.js),它的物件宣告是這樣的:
1: function MathBase(a, b) {
2:
3: var _a = a;
4: var _b = b;
5:
6: this.a = _a;
7: this.b = _b;
8:
9: this.add = function () { return this.a + this.b; };
10: this.minus = function () { return this.a - this.b; };
11:
12: }
其呼叫方式為:
1: function displayAdd() {
2:
3: var math = new MathBase(1, 2);
4: document.write("1 + 2 = " + math.add());
5: document.write("<br />");
6:
7: }
8:
9:
10: function displayMinus() {
11:
12: var math = new MathBase(1, 2);
13: document.write("1 - 2 = " + math.minus());
14: document.write("<br />");
15:
16: }
執行結果為:
今天,我想要在不改變 Math.js 的情況下擴充它的功能,這時除了用 CP 大法 (copy/paste) 以外,我們還可以多用一樣東西:繼承。只是在 JavaScript 中,繼承的作法和以往的方式完全不同。原有的 MathBase 只有加和減,現在要加上乘和除,我們新增一個 MathV1,宣告如下:
1: MathV1.prototype = new MathBase();
2:
3: function MathV1(a, b) {
4:
5: MathV1.prototype.a = a;
6: MathV1.prototype.b = b;
7:
8: console.log(this);
9:
10: this.multiply = function () {
11: return MathV1.prototype.a * MathV1.prototype.b;
12: };
13:
14: this.divide = function () {
15: return MathV1.prototype.a / MathV1.prototype.b;
16: };
17:
18: }
由於 MathV1 要繼承 MathBase 的功能,在 JavaScript 中,我們可以使用 [Object].prototype 屬性來做這件事,它的意思是這個物件的原型是什麼,所以在第一行中,先定義出 MathV1 的原型是來自 MathBase,讓它們有父子關係,這時 Math.prototype 會是 MathBase 的執行個體 (若沒有明確設定,預設值會是物件本身),那麼我們就可以透過 MathV1.prototype 來操作父類別中的物件,包括修改其屬性,或是呼叫方法等等都可以。由於要共用在 MathBase 中宣告的屬性,所以我們在函數中使用的是 MathV1.prototype 來存取宣告在父類別的 a 和 b 屬性。
這時,我們將原本的呼叫程式由 MathBase 改成 MathV1,如下:
1:
2: function displayAdd() {
3:
4: var math = new MathV1(1, 2);
5: document.write("1 + 2 = " + math.add());
6: document.write("<br />");
7:
8: }
9:
10:
11: function displayMinus() {
12:
13: var math = new MathV1(1, 2);
14: document.write("1 - 2 = " + math.minus());
15: document.write("<br />");
16:
17: }
18:
19: function displayMultiply() {
20:
21: var math = new MathV1(1, 2);
22: document.write("1 * 2 = " + math.multiply());
23: document.write("<br />");
24:
25: }
26:
27:
28: function displayDivide() {
29:
30: var math = new MathV1(1, 2);
31: document.write("1 / 2 = " + math.divide());
32: document.write("<br />");
33:
34: }
35:
36:
然後在瀏覽器中使用:
你會發現結果一和結果二都可以正常運作,而且變數是共用的。
Object.prototype 還有幾個不同的用法,像是:
1. 呼叫物件內指定的方法和屬性,但僅能針對公開屬性和方法呼叫,對於私有成員不行。
2. 更改建構式內容 (Object.prototype.constructor)。
3. 擴充現有物件,在不使用本文手法之下,這部份可參考:http://www.javascriptkit.com/javatutors/oopjs2.shtml。
經過以上討論,你會發現 JavaScript 的繼承不像是 C# 或 Java 這樣的,由物件本體 (object context) 去繼承,而是透過函數的方式產生的效果,也就是使用 prototype 屬性取代 base (JavaScript 中沒有 base 屬性),同時 prototype 又允許 JavaScript 自由擴充物件,我們後面的幾個特殊功能會利用到這個特性。
Reference:
http://www2.stat.unibo.it/palareti/studenti/linguaggi/jsobj/index.htm
http://webreflection.blogspot.com/2009/06/wait-moment-javascript-does-support.html