JavaScript 快速導覽 - 繼承

JavaScript 是採原型基礎 (prototype-based) 的物件導向程式設計 (object-oriented programming, OOP) 的程式語言,自然自行定義的物件 (object) 也具有繼承機制 (inheritance) 。所謂的繼承是指物件可以有來源關係,後來定義的物件可以繼承之前定義物件的屬性 (atribute) 與方法 (method) 。



「繼承」是物件導向程式設計中一個重要也是核心的觀念,動詞原文為 inherit 。雖然 inherit 在英文的意思泛指從什麼得到什麼,用作中文「繼承」說得通,也能用作「遺傳」。然而中文的「繼承」隱含某物不再,另物將起的意思,譬如「我繼承某某的精神」,雖然某某不見得已死,未來將要付出努力的卻是我而非某某。因而這裡的意思中文用「遺傳」比較恰當,子代會從親代遺傳某些生物特性,子代與親代也會並存一段時間,這就沒有某物不再的意含了。然而這裡我們仍沿用程式設計常用的「繼承」一詞,但仍提出意見以免讀者混淆。


舉例如下
function run() {
    var c = document.getElementById("content");
    var n1 = document.createElement("p"); 
    var n2 = document.createElement("p"); 
    var n3 = document.createElement("p"); 
    var n4 = document.createElement("p");
    var n5 = document.createElement("p");
    var n6 = document.createElement("p");
    
    var t1 = new Elephant();
    var t2 = new Elephant();
    var t3 = new Elephant();
    var t4 = t1.speak();
    var t5 = t2.speak();
    var t6 = t3.speak();
    
    n1.appendChild(document.createTextNode(t1));
    c.appendChild(n1);
    n2.appendChild(document.createTextNode(t2));
    c.appendChild(n2);
    n3.appendChild(document.createTextNode(t3));
    c.appendChild(n3);
    n4.appendChild(document.createTextNode(t4));
    c.appendChild(n4);
    n5.appendChild(document.createTextNode(t5));
    c.appendChild(n5);
    n6.appendChild(document.createTextNode(t6));
    c.appendChild(n6);
}

function Animal() {
    this.age = 0;
    this.weight = 0;
}
Animal.prototype.speak = function() {
    return "哈囉,我已經" + this.age + "歲,有" + this.weight + "公斤重"
}

function Elephant() {
    this.wname = "nobody";
}
Elephant.prototype = new Animal;

/* 《程式語言教學誌》的範例程式
     http://pydoing.blogspot.com/
     檔名:herit01.js
     功能:示範 JavaScript 程式 
     作者:張凱慶
     時間:西元 2010 年 11 月 */


利用以下的 HTML 文件開啟
<html>

<head>
<title>JavaScript Demo</title>
<script src="herit01.js" type="text/javascript"></script>
</head>

<body>
<input id="b" type="button" value="RUN" onclick="run();">
<div id="content"></div>
</body>

</html>

<!-- 《程式語言教學誌》的範例程式
     http://pydoing.blogspot.com/
     檔名:herit01.html
     功能:示範 JavaScript 程式 
     作者:張凱慶
     時間:西元 2010 年 11 月 -->


執行結果如下



我們在第 31 行定義了 Animal 物件的建構子函數 (constructor function) ,並且定義了一個 speak() 方法
function Animal() {
    this.age = 0;
    this.weight = 0;
}
Animal.prototype.speak = function() {
    return "哈囉,我已經" + this.age + "歲,有" + this.weight + "公斤重"
}


然後在第 39 行定義另一個 Elephant 物件
function Elephant() {
    this.wname = "nobody";
}
Elephant.prototype = new Animal;


繼承定義在第 42 行
Elephant.prototype = new Animal;


我們將 Elephant 物件的 prototype 屬性指派一個新的 Animal ,因此 Elephant 物件得以繼承 Animal 的屬性及方法,所以 run() 函數 (function) 中所新建的三個 Elephant 物件,可以呼叫執行 speak() 方法 。


然而上面的提供的繼承寫法並不具有彈性,屬性直接提供預設值。以下的寫法在建構子函數中可以提供參數 (parameter)
function run() {
    var c = document.getElementById("content");
    var n1 = document.createElement("p"); 
    var n2 = document.createElement("p"); 
    var n3 = document.createElement("p"); 
    var n4 = document.createElement("p");
    var n5 = document.createElement("p");
    var n6 = document.createElement("p");
    
    var t1 = new Elephant(98, 3000, "林旺");
    var t2 = new Elephant(52, 2456, "伊芙");
    var t3 = new Elephant(12, 1300, "小黑");
    var t4 = t1.speak();
    var t5 = t2.speak();
    var t6 = t3.speak();
    
    n1.appendChild(document.createTextNode(t1));
    c.appendChild(n1);
    n2.appendChild(document.createTextNode(t2));
    c.appendChild(n2);
    n3.appendChild(document.createTextNode(t3));
    c.appendChild(n3);
    n4.appendChild(document.createTextNode(t4));
    c.appendChild(n4);
    n5.appendChild(document.createTextNode(t5));
    c.appendChild(n5);
    n6.appendChild(document.createTextNode(t6));
    c.appendChild(n6);
}

function Animal(age, weight) {
    this.age = age || 0;
    this.weight = weight || 0;
}
Animal.prototype.speak = function() {
    return "哈囉,我已經" + this.age + "歲,有" + this.weight + "公斤重"
}

function Elephant(age, weight, wname) {
    this.base = Animal;
    this.base(age, weight);
    this.wname = wname || "nobody";
}
Elephant.prototype.speak = function() {
    return "我的名字是" + this.wname + "已經" + this.age + "歲"
}

/* 《程式語言教學誌》的範例程式
     http://pydoing.blogspot.com/
     檔名:herit02.js
     功能:示範 JavaScript 程式 
     作者:張凱慶
     時間:西元 2010 年 11 月 */


利用以下的 HTML 文件開啟
<html>

<head>
<title>JavaScript Demo</title>
<script src="herit02.js" type="text/javascript"></script>
</head>

<body>
<input id="b" type="button" value="RUN" onclick="run();">
<div id="content"></div>
</body>

</html>

<!-- 《程式語言教學誌》的範例程式
     http://pydoing.blogspot.com/
     檔名:herit02.html
     功能:示範 JavaScript 程式 
     作者:張凱慶
     時間:西元 2010 年 11 月 -->


執行結果如下



我們提供兩個參數給 Animal() ,其中
this.age = age || 0;
this.weight = weight || 0;


參數會被指派給屬性,這裡連用邏輯或運算子 (operator) ,表示若沒有提供參數,便將 || 之後的預設值指派給屬性。


Elephant 的建構子函數定義在第 39 行
function Elephant(age, weight, wname) {
    this.base = Animal;
    this.base(age, weight);
    this.wname = wname || "nobody";
}
Elephant.prototype.speak = function() {
    return "我的名字是" + this.wname + "已經" + this.age + "歲"
}


Elephant 多一個 base 屬性,其為 Animal ,然後利用 base 呼叫 Animal 的建構子函數,使 Elephant 繼承 Animal 的屬性,最後我們利用物件的 prototype 屬性動態增加 speak() 方法。


注意這裡的 prototype 是屬於動態增加的方式,如果是用 prototype 來繼承,利用 prototype 定義的方法都會被繼承下來。上例中我們利用的是 base 來繼承屬性,因此 Animal 用 prototype 定義的 speak() 並不會被繼承。


中英文術語對照
原型基礎prototype-based
物件導向程式設計object-oriented programming, OOP
物件object
繼承inheritance
屬性attribute
方法method
建構子函數constructor function
函數function
參數parameter
運算子operator






沒有留言: