[JavaScript] JavaScript 物件導向設計 (2): 繼承篇

身為一個物件導向的程式開發人員,應該不會不知道繼承 (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:  }

執行結果為:

image

 

今天,我想要在不改變 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:      

 

然後在瀏覽器中使用:

image

image

 

你會發現結果一和結果二都可以正常運作,而且變數是共用的。

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