Object-oriented Javascript: 如何在Javascript中達到物件導向

Java寫久了以後,對於物件導向的程式寫法已經感到非常自然,
開始接觸Javascript以後,在程式設計上常常覺得窒礙難行,
覺得如果可以用物件該有多好啊!

The Node Craftsman Book是Manuel Kiessling的第二本書,
(第一本是The Node Beginner Book - A comprehensive Node.js tutorial,非常推薦新手完讀)
其中一個章節很完整的介紹了幾種達到類似效果的"模擬"物件導向寫法,
整理記錄在這邊

==========我是分隔線==========

我們要來談一個叫做myCar的物件。
myCar就是一台車子,簡單來說,它有drivehonk的功能。

在Java裡,要表達這輛車子很簡單(吧)
我們會定義一個Car的class,
class裡有兩個函數,一個是drive,一個是honk
接著只要將myCar定義為Car的物件,我就可以讓myCar按下喇叭

Car myCar = new Car();
myCar.honk();

那在Javascript裡呢?

1. Using a simple function to create plain objects. 用函數產生物件


最簡單的方法是定義makeCar函數,這個函數會回傳具有honk動作的車子物件,要幾個就有幾個:

var makeCar = function() {
    var newCar = {};
    newCar.honk = function() {
        console.log('honk honk');
    };
    return newCar;
};

myCar1 = makeCar();
myCar2 = makeCar();

這個方法有一個最大的特色,就是製造出來的每輛車子都互相獨立(indepedent),不共享任何東西。也就是說,當你產生1,000輛車子時,Javascript的編譯器也就存了1,000份一模一樣的honk函數在記憶體裡面。當你需要大量複製使用物件時,這會是非常耗記憶體的作法!

2. Using a constructor function to create objects. 定義一個建構函數來製造物件


在Javascript裡,constructor是一個特別的函數,專門用來產生具有共同行為的物件。這種產生物件的方式其實已經很接近Java裡class的概念,因此它又稱為pseudo class。

constructor的用法是這樣的:

var Car = function() {
    this.honk = function() {
        console.log('honk honk');
    };
};

var myCar = new Car();
console.log(myCar.constructor); // outputs [Function: Car]

Car是constructor函數,使用this可以定義物件共同的行為,使用new就可以產生物件。

3. Using prototyping to efficiently share behavior between objects. 使用prototyping. 定義物件共享的行為


直接從prototyping的用法開始介紹:

var Car = function() {};

var.prototype.honk = function() {
    console.log('honk honk');
};

var myCar = new Car();

Car是一個空的constructor function。在Javascript中,函數也是一種物件,所以函數也具有propoties。prototype就是constructor的其中一個propoty,讓我們可以定義honk成為Car共享的函數。

如果現在我想讓myCar按喇叭(myCar.honk()),Javascript執行的流程是這樣的:在myCar物件尋找honk函數來執行,如果找不到的話,就從myCarprototype裡找。

這樣的流程讓honk的定義具有一些彈性,例如:

1) 在runtime改變prototype函數的行為


var Car = function() {};

Car.prototype.honk = function() {
    console.log('honk honk');
};

var myCar = new Car();

myCar.honk(); // executes Car.prototype.honk() and outputs "honk honk"

Car.prototype.honk = function() {
    console.log('meep meep');
};

myCar.honk(); // executes Car.prototype.honk() and outputs "meep meep"

2) 在runtime新增prototype函數


var Car = function() {};

Car.prototype.honk = function() {
    console.log('honk honk');
};

var myCar = new Car();

Car.prototype.drive = function() {
    console.log('vrooom...');
};

myCar.drive(); // executes Car.prototype.drive() and outputs "vrooom..."


3) 物件可以個別自訂函數的行為 (意義上類似Java Override)


var Car = function() {};

Car.prototype.honk = function() {
    console.log('honk honk');
};

var myCar1 = new Car();
var myCar2 = new Car();

myCar1.honk(); // executes Car.prototype.honk() and outputs "honk honk"
myCar2.honk(); // executes Car.prototype.honk() and outputs "honk honk"

myCar2.honk = function() {
    console.log('meep meep');
};

myCar1.honk(); // executes Car.prototype.honk() and outputs "honk honk"
myCar2.honk(); // executes myCar2.honk() and outputs "meep meep"

prototype其實是非常有趣的一種用法,透過一連串的prototype指定,Javascript還可以實現Java中的inheritance概念。

假設我們說CarBike都是一種交通工具(Vehicle),Vehicle共同的行為就是可以駕駛(drive),不過Car有喇叭可按(honk),但Bike是按鈴(ring)。

在Javascript中可以使用prototype來描述這兩種交通工具:

var Vehicle = function() {};

Vehicle.prototype.drive = function() {
    console.log('vrooom...');
};

var Car = function() {};
var Bike = function() {};

Car.prototype = new Vehicle();
Bike.prototype = new Vehicle();

Car.prototype.honk = function() {
    console.log('honk honk');
};
Bike.prototype.ring = function() {
    console.log('ring ring');
};

var myCar = new Car();
var myBike = new Bike();

myCar.drive(); // outputs "vrooom..."
myBike.drive(); // outputs "vrooom..."

myCar.honk(); // outputs "honk honk"
myBike.ring(); // outputs "ring ring"


4. 使用Object.create()使得物件彼此繼承


讓我們直接從Object.create函數的定義開始介紹

Object.create = function(o) {
    var F = function() {};
    F.prototype = o;
    return new F();
};

傳入物件 o,Object.create函數會回傳一個繼承o的constructor函數F

回到prototype的例子,我們現在使用Object.create來描述一輛屬於交通工具的汽車:

var vehicle = {};
vehicle.drive = function () {
    console.log('vrooom...');
};

var car = Object.create(vehicle);
car.honk = function() {
    console.log('honk honk');
};

var myCar = Object.create(car);

myCar.honk(); // outputs "honk honk"
myCar.drive(); // outputs "vrooom..."

vehicle是一個具有drive行為的物件,我們使用Object.create產生另一個物件car,除了繼承drive行為以外,我們又賦予它新的honk行為,最後再用一次Object.create產生又能honk又能drivemyCar物件。

這個用法完整呈現了Javascript與Java的不同:Java的物件導向是建立在object-class的關係上,而Javascript則是object-object的概念。透過object-object來實踐物件導向的程式,就稱為Prototype-based programming。

留言