那些關於 JS Prototype 的事

物件導向?Javascript 是物件導向 (OOP) 語言嗎?

關於 JavaScript 是不是物件導向語言的爭議,大家眾說紛紜。而事實是 JS 就是物件導向程式語言。但是 JavaScript 的物件導向非常特殊,是基於原型 (Prototype-Based) 的物件導向。

物件導向程式設計(OOP)的基本概念是使用物件的方式來表徵,模擬真實世界中的事物並將物件作為程式設計的基本單元,注重物件的覆用性,因此可以看到 OOP 的三大特性是「封裝、繼承、多型」,藉由隱藏方法的具體執行動作而注重介面的操作。

類別 (Class) 與物件(Object),Class 是定義了抽象事物的的內部資料以及可操作的方法(Method),而物件是類別的實做(Instance)。而類別擁有其他類別的所有項目並且提供更多的資料集方法,這稱為繼承。然而不同繼承的子類型在面臨同樣方法的呼叫時,做出不同的反應,這就是多型。

原型鏈 (Prototype Chain) 與 JavaScript 的物件導向

JS 中的物件導向則是透過原型鏈實做,所有的 JS 物件中都有一個原型物件 (Prototype Object )來作為該物件的原型參考。然而這個原型物件中又會有自己的原型物件,這樣的鏈節稱之為 Prototype Chain。

JS 的物件是利用 Constructor Function (創建函數)來定義奇特性的。舉個例子

// 創建一個原型 Dot
function Dot(x, y) {
    this.x = x; this.y = y
}

// 為 Dot 建立一個實例,dotP 的原型將會是 Dot
const dotP = new Dot(1, 2); // => {x: 1, y"2}

// 利用 object.constructor.name 可以看到 dotP 的 constructor function 的名稱
console.log(dotP.constructor.name); // => 'Dot'

// 為 Dot prototype新增一個方法 printDot 
Dot.prototype.printDot = function () {
    return {x: this.x,y: this.y};
}

// 實用 dotP 的 printDot 方法
console.log(dotP.printDot()); // {x: 1, y: 2}

// 也可以幫 prototype 新增屬性,但通常很少這麼做
Dot.prototype.lazp = "lazplazp";

然而原型鏈的繼承開怎麼操作呢?

Dog 在繼承了 Animal 後就獲得了 Animal 的屬性和方法,因此這時候建立 Dog 的實例 lazp 也能執行父類別的方法。

小補充:.prototype.__proto__的差異可以很顯著的在上述例子中看出,prototype 指的是類的抽象定義(包含屬性和方法), __proto__ 則是只該物件的原型物件。

但其實,JS 的物件無所不在

JS 中幾乎所有東西都是物件,JS 有七大主要型別,number, string, symbol, boolean, null, undefined, object。可以看到,除了前六個之外的所有東西就都是物件。

ES6 中的神奇語法堂

在 JS 中原本的原型對於其他程式語言非常的不一樣,因次在 ES6 標準中新增了類似於常見的類別的語法糖。前述例子在新語法中的實做如下

雖然 ES6 中多了 Class 的語法,但是 JS 的實做仍是以 Prototype Chain 實做,因此原本的語法與概念仍然適用。

結語

今天更認識了 JS Prototype 以及 OOP 與 JS 的關係,JS 因為許多歷史元素,造就了許多有趣的語法,更認識 JS 後,在撰寫程式上會更有概念!

Last updated