JavaScript 與物件…導向?

陳劭恩 Sean, Shao En Chen
Practicode
Published in
5 min readMay 15, 2017

前幾天和朋友野餐聊天時討論到,JavaScript 到底是不是物件導向程式語言呢?結果相當(不)意外的,它的確是。然而,JS 和以往寫 C、Java 時碰到的「物件導向」有不少差異。因為最近看了不少相關的文章,希望把一些有趣的事實與設計記錄在這邊,作為自己近期的學習筆記。

JavaScript 是個物件導向語言。—— JavaScript 設計模式

《JavaScript 設計模式》在第一章就開宗明義地告訴我們 JavaScript 的特性。在 JS 裡,幾乎大部分的東西都是物件,包含函數,他們擁有自己的屬性(properties)與方法(methods)。舉例來說大概像是這樣:

Example of JS Object

如上所見,要定義一個 JavaScript 的物件,不像 Java 要做:

Fruit fruit = new Fruit(); // Java 創建物件的方式

JavaScript 的物件是一組 key — value 的對應,就像是一個空白的 Array 或是 Vector。當你創建一個新的物件,他會開啟一個「空白」的物件(當然,裡面預設有一些屬性,等等會提到),我們再接著把數值、函數或是其他物件丟進去。

和 C/C++、Java 等語言不同,JavaScript 沒有 Class,儘管在 ES6 引入了類別(classes)作為 JavaScript 現有 Prototype-based 繼承的語法糖(Class — MDN)。在《JavaScript 設計模式》中提到該語法糖能夠精簡你的程式碼,讓 JavaScript 專心處理物件。

在此先不提 ES6 提供的語法糖,JavaScript 透過原型鍊(Prototype Chain)建立從屬關係,實作繼承關係。舉例來說,剛剛提到 Java:

Fruit fruit = new Fruit(); // Java 創建物件的方式

Java 在 new 之後以類別去創建新的物件。然而 JavaScript 沒有 Class,因此在 JavaScript 裡面可能是這樣的:

function Fruit(name, number, ...) {
this.name = name;
this.number = number;
this.type = "fruit";
...
this.getName = function() { console.log(this.name); }
}
var apple = new Fruit(“apple”, 10, …); // 以構造函數建立物件// 並且構造函數可以包含方法
apple.getName();

不過,當今天的情境變成了這樣:

var apple = new Fruit(“apple”, 10, …);
var banana = new Fruit(“banana”, 20, …);

apple 和 banana 將會有各自的 “type” 屬性以及方法,儘管他們的內容是一樣的東西,卻會佔據兩份空間。如果可以將這些共同的屬性與方法存放在同一個空間,應該會是很好的一件事。

因此我們在構造函數上增加了 prototype 屬性,只要將這些共同的屬性與方法存在該函數的 prototype 上,就可以讓這些屬於同一「類別」的物件共用,以節省空間。這就是「原型」的基本概念,創建出的物件因而可以繼承構造函數的 prototype。

Fruit.prototype.type = "fruit";

而實際上創建出來的物件會有一個屬性: __proto__。這個屬性會指向構造函數的 prototype。當我們呼叫該物件的屬性或方法,首先將從該物件自身的屬性及方法尋找。若是找不到,則會向上往該物件的 __proto__ 找,直到找到相對應的屬性或方法,或是無法繼續向上尋找——這就是原型鍊的概念。也就是說,創建出來的物件大概是這樣的:

var apple = new Fruit(“apple”, 10, …); // 以構造函數建立物件apple.__proto__ == Fruit.prototype // true

因此,若今天 apple 物件要尋找 “type” 屬性,程式將會先從 apple 自有的屬性尋找。由於該屬性已被存在 Fruit 的 prototype 當中,此時應找不到,程式便從 apple. __proto__ 取得 Fruit.prototype,並從該處取得 “type” 屬性。我們可以藉由 hasOwnProperty() 與 instanceof 方法驗證:

apple.hasOwnProperty("type"); // false
apple.__proto__.hasOwnProperty("type"); // true
apple instanceof Fruit // true

因此,藉由建立物件彼此間的原型鍊關係,我們可以在 JavaScript 達成繼承關係的功能。

--

--