2011/08/19

javascript的物件導向程式設計

javascript 是一種 prototype-based 的語言,在 OOP 的寫法上相較於 java 更為麻煩,所以特別研究了一下幾篇文章,順便紀錄一下心得,免得以後忘記。


物件的建立
一種是透過 new 關鍵字
//這個牽涉到建構子,稍後會提到
var banana = new Fruit();
另一種則是使用 {} 宣告一個物件
//隱含的建立一個 javascript 內建的 Object 物件
var banana = {
 //物件屬性
 color: 'yellow',
 size: '15cm',
 //物件方法
 destroy: function() {
  alert('Noooooooooo!');
 }
};


建構子(Constructor)、物件屬性和物件方法的宣告
使用 function 宣告不只是可以建立函式,也可以建立一個建構子。而屬性和方法可以在建構子內宣告,這種方式相當於在每個物件建立時才把屬性和方法附加在物件上;不過這樣做有個缺點,如果父類別採用這種方式宣告,當存取父類別的屬性或方法時,會變成 undefined,因為屬性和方法是附加在每個建立好的物件上,而不是類別本身。
//建構子乍看之下就只是一個函式而已
function Fruit(name, color) {
 //私用屬性(private)
 //其實就是區域變數的概念而已
 var secret = 'You cant see this property.';
 //公用屬性(public)
 this.name = name;
 this.color = color; 
 
 //私用方法(private)
 function doNothing() {};
 //公用方法(public)
 this.changeColor = function(newColor) {
  this.color = newColor;
 }
}
之後便可以透過 new 來產生新的類別實體
//建立類別實體
var fruit1 = new Fruit('banana', 'yellow');
var fruit2 = new Fruit('apple', 'red');

alert(fruit1 instanceof Fruit); //true
alert(fruit1 === fruit2); //false
alert(fruit1.color); //yellow

fruit1.changeColor('green');
alert(fruit1.color); //green

alert(typeof fruit1.secret); //undefined
alert(typeof fruit1.doNothing); //undefined
alert(typeof fruit1.stillDoNothing); //undefined
但是如果直接對變數宣告屬性或方法,只會附加在目標變數指向的物件上,而不是類別本身。
//直接對變數宣告屬性
fruit1.looks = 'Like a banana.';
var fruit3 = new Fruit('grape', 'purple');

alert(fruit1.looks); //Like a banana.
alert(typeof fruit3.looks); //undefiend


javascript 的 prototype 與繼承(Inheritance)
在 javascript 中物件的繼承跟 prototype 這個屬性有很大的關係。

在取出物件的屬性時,會先去檢查該物件是否有直接定義目標屬性;若是沒有,則檢查該物件的 prototype 物件有沒有定義,如果還是沒有,會往 prototype chain (透過繼承將 prototype 鏈結起來)的上一層找,直到根部的 prototype 為止。特別要注意的是所有的物件都會隱含的參照到一個 prototype,但是只有 Function 物件(建構子)可以直接存取 prototype 屬性。

而物件的屬性和方法比較正規的宣告方式也是使用 prototype 來宣告,這樣做有一個好處,就是可以設定屬性初始值。
//改用繼承的方式來寫
//水果建構子
function Fruit(name) {
 this.name = name ? name : this.name;
}

//使用 prototype 作宣告並初始化
Fruit.prototype.name = 'Fruit';
Fruit.prototype.inFruit = 'inFruit';
Fruit.prototype.show = function() {
 alert(this.name);
};

//香蕉建構子,香蕉是一種水果我跟你說
function Banana() {
 //呼叫父類別的建構子
 this.super.call(this, 'Banana');
}

//繼承自 Fruit
//也是把 Banana 接上 Fruit 的 prototype chain
Banana.prototype = new Fruit();
//修正 Banana 的建構子指標,根據網路上的說法他會指向 Fruit 的建構子
//但測試後,有沒有這行其實沒差,因為上面已經宣告過 Banana 的建構子了
Banana.prototype.constructor = Banana;

//使用 prototype 作宣告並初始化
Banana.prototype.super = Fruit; //父類別參照(super)得自己宣告,
                                //因為 javascript 不提供直接參
                                //照到父類別的變數
Banana.prototype.color = 'yellow';
Banana.prototype.changeColor = function(newColor) {
 this.color = newColor;
};
//覆寫父類別的function
Banana.prototype.show = function() {
 alert(this.name + '\'s color is ' + this.color + '.');
};

var banana = new Banana();
alert(banana instanceof Banana); //true
alert(banana instanceof Fruit); //true
alert(banana.inFruit); //inFruit
banana.show(); //Banana's color is yellow.

banana.changeColor('green');
banana.show(); //Banana's color is green.
若要存取父類別的屬性或方法,還是要透過 prototype 來取得。
alert('Parent\'s name is ' + banana.super.prototype.name + '.');
                                       //Parent's name is Fruit.
banana.super.prototype.show(); //Fruit

沒有留言:

張貼留言