理解JavaScript的原型屬性

alvendarthy發表於2016-06-21

理解 JavaScript 的prototype屬性不太容易。你也許知道它同物件導向程式設計(OOP)和物件繼承有關,但未必對其技術原理非常清楚。

原型繼承

物件導向程式設計可以通過很多途徑實現。其他的語言,比如 Java,使用基於類的模型實現: 類及物件例項區別對待。但在 JavaScript 中沒有類的概念,取而代之的是一切皆物件。JavaScript 中的繼承通過原型繼承實現:一個物件直接從另一物件繼承。物件中包含其繼承體系中祖先的引用——物件的 prototype 屬性。

class 關鍵字是在 ES6 中首次引入 JavaScript 的。其實,它並沒有為物件導向繼承引入新模型, class 關鍵字通過語法糖,實現了本文介紹的原型特性和建構函式。

JavaScript 實現繼承的語言特性

以下語言特性共同實現了 JavaScript 繼承。

  • 當嘗試訪問 JavaScript 物件中不存在的屬性時,解析器會查詢匹配的物件原型。例如呼叫 car.toString(),如果 car 沒有 toString 方法,就會呼叫 car 物件的原型。 這個查詢過程會一直遞迴, 直到查詢到匹配的原型或者繼承鏈盡頭。
  • 呼叫  new Car() 會建立一個新的物件,並初始化為 Car.prototype。 這樣就允許為新物件設定原型鏈。需要注意的是,new Car() 只有當  Car 是函式時才有意義。 此類函式即所謂建構函式。
  • 呼叫物件的一個成員函式時, this 的值被繫結為當前物件。例如呼叫 "abc".toString()this 的值被設定為 "abc",然後呼叫 toString 函式。該技術支援程式碼重用:同樣的程式碼,可在 this 為各種不同的值時呼叫。物件的成員函式,也被稱為物件的方法。

舉個例子

我們用物件導向程式設計,實現一個計算矩形周長的例子。

首先,我們定義建構函式 Rectangle。 按照規範,我們大寫建構函式名首字母,表明它可以用 new 呼叫,以示與其他常規函式的區別。建構函式自動將 this 賦值為一空物件,然後程式碼中用 xy 屬性填充它,以備後用。

然後, Rectangle.prototype 新增一個通過 xy 屬性計算周長成員函式。 注意 this 的使用,在不同的物件中,this 會有不同的值,這些程式碼都可以正常工作。

最後, 一個名為 rect 的物件建立出來了。 它繼承了 Rectangle.prototype, 我們可以呼叫 rect.perimeter(), 然後將結果列印到控制檯。

prototype 屬性名稱帶來的誤解

有一些關於 JavaScript 的原型的誤解。 一個物件的原型與物件的 prototype 屬性並非一回事。 前者用於在原型鏈中匹配不存在的屬性。後者用於通過 new 關鍵字建立物件,它將作為新建立物件的原型。 理解二者的差異,將幫助你徹底理解 JavaScript 中的原型特性。

在我們的例子中, Rectangle.prototype 是用 new Rectangle() 建立出來物件的原型, 而 Rectangle 的原型實際上是 JavaScript 的 Function.prototype。(子物件的原型是父物件的 prototype 屬性)

物件中儲存原型的變數,也被稱之為內部原型引用(the internal prototype link),歷史上也曾稱之為 __proto__ ,對這個稱謂始終存在一些爭議。 更精確的,它可以被稱為 Object.getPrototypeOf(...) 的返回值。

相關文章