JavaScript 的 prototype(上)

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

前言

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

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

然後在 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。

  • hsiang

    就我了解,就算先new了function實體,之後再利用prototype新增屬性,過去new 出的實體還是增加了相同的屬性。所以你說的 “對一個 Function 的實體而言,它有一個成員叫 prototype 的 Object,對它做的定義,便會隨著 new 在生成新的物件時,把這些定義帶過去”是不是有點錯誤?

  • @hsiang
    的確,你說得對…

  • TonyQ

    我一直覺得這很像是java中的 static 概念 😛

    由類別所共享的靜態類別成員.

  • 3色波斯

    期待下集

  • Suyuan Chang

    下集勒… (敲碗)

  • 因為….想等某教材做完之後再寫 XD

  • 同行

    寫的好棒, 期待你的下集!
    譬喻解釋的方式也很好(剛好我也專長java.cc++)

  • 其實嚴格來說,是本體(new出來的東西)找不到指定的屬性時,會去他的 prototype 找,找不到時會再找 prototype 的 prototype ,一直找到最基本幾乎什麼都沒有的 Object 為止。

  • Eric Xu

    怎麼都沒有(下)?討厭! 😛

  • Paul

    敲碗

  • JU

    您好~想請教一個問題
    文章中說 “先用 new 生出 Function 的實體,之後再對該 Function 做 prototype 的新定義,被生出來的實體一樣會採用新的 prototype 定義”

    但實際測試

    var bar = function() {}
    var n2 = new bar;

    bar.prototype = {

    x: 123,
    y: function() { … }
    };

    console.log(n2.x);

    會是undefined?
    不好意思 因為還很菜
    有些觀念不是很清楚~不知道是不是誤會文中的意思

    • JU

      抱歉!突然看懂了!!

      var bar = function() {}
      var n2 = new bar;
      bar.prototype.x = 123;

      console.log(n2.x);

      這樣就行了! 因為原本是個object