這篇文章會整理我目前研究 JavaScript 的 prototype 心得。

前言

過去我們在學習一些流行的程式語言(如:C++, Java, Python 等)時,都已經很習慣他們本身的「物件導向」模式,大致上可以有這樣共通的概念:

  1. class 這樣的關鍵字宣告一個「類別」(class),然後在此類別下定義一些成員函式(通常會稱為 method)或變數。然後依照這樣類別的定義,運用 new 關鍵字來分別生成此類別的實體(instance)(註:Python 不必用 new)。
  2. 這些「實體」互相使用不同的記憶體空間,所以它們彼此的資料(成員變數的值等等)並不會受到干擾。
  3. 類別可以繼承,藉此來實現「介面」(interface)、「虛擬」等等概念。

然後在 JavaScript 裡,雖然有物件的概念,卻沒有上述「類別」的概念及實作,有的則是 prototype

Object? Function?

在講 prototype 之前,先講一下 JavaScript 裡的「物件」,JavaScript 雖然是一個弱資料型別的語言,不過它還是有基本的資料型態以及複雜的資料型態,其中 ObjectFunction 是最基本的複雜資料型態,這裡你可以做個簡單的實驗:(假設你有裝 Firefox + firebug)

var foo = {};
var bar = function() {};  // 也可以 function Bar() {};

if (foo instanceof Object) {
  alert('Yes, foo is an Object instance');
}

if (bar instanceof Function) {
  alert('Yes, bar is a Function instance');
} 

console.log(typeof(foo));  // 輸出 "object"
console.log(typeof(bar));  // 輸出 "function"

這裡用了 instanceof 運算子及 typeof 函式,用來觀察 foobar 的資料型態。不過如果你再執行這一段程式碼(續上述程式碼):

var c = new bar;
console.log(typeof(c));  // 輸出 "object"

我們運用了 new 關鍵字來產生一個 instance c,雖然 bar 的資料型態是 Function,但是它生出來的 instance 卻是一個 Object,但如果你今天執行的是 var c = new foo;,瀏覽器則是會跟你抱怨 foo 不是一個 Function 的資料型態喔!

prototype

熟悉 JavaScript 的人就知道,我們可以在一個 Object 下定義變數及函式,像是這樣:

var foo = {
  x: 100,
  y: 200,
  f: function() {
    ....
  }
};

如此一來你可以 foo.x, foo.y 或是 foo.f() 來操作。那上面提到的部份,既然從 Function 所 new 出來的是 Object 的實體,那是不是也可以透過一些定義的手段讓產生出來的實體也有這些成員變數或函式呢?有的,像是這樣:(續上述的 code)

bar.prototype = {
  x: 123,
  y: function() { ... }
};

var n = new bar;
n.x = ...;
n.y();

對一個 Function 的實體而言,它有一個成員叫 prototypeObject,對它做的定義,便會隨著 new 在生成新的物件時,把這些定義帶過去,這是不是就很像在 C++/Java 這類語言上「類別」的概念呢?

UPDATE: [感謝 hsiang 網友的指正]
即使先用 new 生出 Function 的實體,之後再對該 Function 做 prototype 的新定義,被生出來的實體一樣會採用新的 prototype 定義。也就是對每一個實體而言,它綁住的是一個 prototype 而不是一個 class。

那這樣做跟我直接寫成 bar.x = 123; 或是 bar.y = function() {...} 有什麼不同呢?如果你這麼做的話,就只能用 bar.xbar.y() 來使用你定義的東西,無法在 new 的時候帶到新的物件上。

既然我們是 new 一個 Function,那這個 function 裡面的程式碼又代表什麼意義?又什麼時候會被執行呢?其實這時它就像是傳統「類別」中的「建構子」(constructor),所以當我在 new bar; 或是 new bar(); 時,其實就會去呼叫 bar 這個 function 的內容了,而且,其實 bar === bar.prototype.constructor 喔!

下一篇文章將會說明如何在這種架構下做到繼承、實作以及 mix-in。

 

歷史上的今天