基于原型编程

**基於原型編程**（英語：prototype-based programming）或稱為**基於原型的編程**、**原型編程**，是物件導向程式設計的一種風格和方式。在原型編程中，行為重用（在類別為基的語言通常稱為繼承），是通過複製已經存在的原型物件的過程實現的。這個模型一般被認為是無類的、面向原型、或者是基於實例的編程。

原型編程最初的（也是最經典的）例子是程式語言 Self，它是由 David Ungar 和 Randall Smith 開發的。但是無類編程方式最近變得越來越受歡迎，並且被 JavaScript、Cecil、NewtonScript、Io、REBOL，還有一些其他的程式語言所採納。

## 與類別為基編程的比較

在類別為基編程當中，物件總共有兩種類型：類和介面。類包含儲存資料的結構和操縱資料的行為，結構是用資料欄位描述的，而行為是通過方法定義的。介面是不包含欄位的抽象類型，通常定義類必須實現的行為，介面不能實例化而必須被實現。所有的類通過提供結構和行為來實現一個介面。類可以從現存的類繼承而來，從而建立一種類層級。

原型編程的主張者經常爭論說，類別為基的語言提倡使用一個關注分類和類之間關係的開發模型。與此相對，原型編程看起來提倡程式設計師，關注一系列物件實例的行為，而之後才關心如何將這些物件劃分到最近的使用方式相似的原型物件，而不是分成類。因為如此，很多基於原型的系統提倡執行時原型的修改，而只有極少數類別為基的物件導向系統（比如第一個動態物件導向的系統 Smalltalk），允許類在程式執行時被修改。

原型編程常與認知心理學的特定思想流派有關，同樣強調「prototypes」和「exemplars」作為學習過程中的關鍵詞。

考慮到絕大多數基於原型的系統，是基於解釋型的和動態型別程式語言，這裡要重點指出的是，靜態型別語言實現基於原型從技術上是可行的。用基於原型編程描述的 Omega 語言，就是這樣系統的一個例子。儘管根據 Omega 網站所述，Omega 也不是完全的靜態，但是可能的時候，它的編譯器有時會使用靜態繫結來改行程序的效率。

## 物件構造

在類別為基的語言中，一個新的實例通過類構造器和給構造器的可選的參數來構造。在基於原型的語言中，沒有顯式的類，物件直接通過一個原型屬性從其他物件進行繼承，這個原型屬性，在 JavaScript 中叫做 `prototype`，在 Io 中叫做 `proto`。在基於原型的系統中，構造物件有兩種方法，通過複製（cloning）已有的物件，或者通過擴充空（nihilo）物件建立，因為大多數系統提供了不同的複製方法，擴充空物件的方式並不顯著。

提供擴充空物件建立的系統允許物件從空白中建立，而無需從已有的原型中複製。這樣的系統提供特殊的文法，用以指定新物件的行為和屬性，無須參考已存在的物件。在很多原型語言中，通常有一個 Object 原型，其中有普遍需要的方法。它被用作所有其它物件的最終原型。擴充空物件建立可以保證新物件不會被頂級物件的命名空間污染。例如在 JavaScript 中，可以利用 `null` 原型來做到，比如 `Object.create(null)`。

複製指一個新物件通過複製一個已經存在的物件（就是他的原型）來構造自己的過程。於是新的物件擁有原來物件的所有屬性，從這一點出發新物件的屬性可以被修改。在某些系統中，子物件持有一個到它原型的直接連結（經由授權或類似方式）。並且原型的改變同樣會導致它的副本的變化。其他系統中，如類 Forth 的程式語言 Kevo，在此情況下不傳播原型的改變，而遵循一個更加連續的模型，其中被複製的物件改變不會通過他的副本傳播。

下面是個在 JavaScript 1.8.5 以上版本的例子（參見 ECMAScript 5 相容性表格）

## 委託

在使用委託的基於原型的語言中，語言執行時能夠分派正確的方法或找到正確的資料，只需遵循一系列委託指標（從物件到其原型）直到找到匹配項。在物件之間建立這種行為共享所需的只是委託指標。與類別為基的物件導向語言中的類和實例之間的關係不同，原型與其分支之間的關係不要求子物件在此連結之外與原型具有記憶體或結構相似性。因此，子物件可以隨著時間的推移繼續被修改和修正，而無需像在類別為基的系統中那樣重新安排其相關原型的結構。要注意，同樣重要的是，不僅可以添加或更改資料，還可以添加或更改方法。出於這個原因，一些基於原型的語言將資料和方法都稱為「槽」（slot）或「成員」。

## 串接

在串接原型（Kevo 程式語言實現的方法）中，沒有可見的指標或連結指向克隆物件的最初原型。原型（父）物件被複製而不是連結到，並且沒有委託。因此，對原型的更改不會反映在克隆物件中。

這種安排下的主要概念差異是對原型物件所做的更改不會自動傳播到克隆。這可能被視為優點或缺點（然而，Kevo 確實提供了額外的原語，用於基於物件的相似性 —— 所謂的家族相似性或克隆家族機制 —— 而不是像委託模型中典型的那樣通過分類起源發布更改）。有時還聲稱基於委託的原型設計還有一個缺點，即對子物件的更改可能會影響父物件的後續操作。然而，這個問題不是基於委託的模型所原生的，也不存在於基於委託的語言（如 JavaScript）中，它確保對子物件的更改總是記錄在子物件本身中，而不會記錄在父物件中（即子物件的 value 會影響父級的值，而不是更改父級的值）。

這樣做的好處包括，物件的作者可以修改這份副本，而無須擔心對此父類別的其他子類產生副作用。進一步的優點，是尋找屬性運算的消耗同授權相比大大降低了，授權尋找必須遍歷整個委託鏈才能判定不存在。

串接的壞處包括傳播變化到整個系統的難度；如果一個變化作用到某個原型，它不會立即或者自動的對它的所有副本生效。然而 Kevo 提供了額外的在物件系統中傳播變化的方式。這種方式是基於他們的相似性（所謂的 family 相似），而非像委託模型具有代表性的那樣源自分類學。

另外一個壞處是在這個模型的大多數自然的實現下，每一個副本上都有額外的記憶體被浪費掉了（相對委託模型而言），因為副本和原型之間有相同的部分存在。然而，在共享的實現和後台資料中提供串接行為的編程編程是可行的。這種做法為 Kevo 所遵從。

## 批評

那些經常批評基於原型系統而支援類別為基的物件模型的人，通常有類似靜態型別系統相對於動態型別系統的擔心。通常這些擔心是：正確性、安全性、可預測性以及效率。

在前三點上，類可以看作和類型等效（多數靜態語言遵守此規則），而且提供保證他們實例的契約，而對這些實例的使用者保證特定場景中的行為。

在最後一點上，效率，類的聲明簡化了編譯器的組織，允許開發高效的方法以及實例變數尋找。對 Self 語言來說，大多開發時間都消耗在開發、編譯以及解釋技術，用以改進基於原型的系統相對於類別為基的系統的效能。舉例來說 Lisaac 產生的代碼速度幾乎跟 C 一樣快。測試是由 MPEG-2 編碼器的 Lisaac 版本得出的，它由一個 C 語言版本複製而來。測試顯示，Lisaac 版本比 C 版本慢 1.9%，但代碼行數少了 37%。然而 C 語言並非物件導向語言，而是一個程序式語言。Lisaac 跟 C++ 版本相比可能更說明問題。

最普遍的對基於原型的語言的批評，來自不喜歡它的軟體開發者社群，僅管 JavaScript 有著人氣和市場。對基於原型系統的了解程度，似乎因為 JavaScript 框架的廣泛應用，以及 JavaScript 針對 web 2.0 的複雜應用而改變。很可能由於這些原因，在 ECMAScript 標準的第四版開始，尋求使 JavaScript 提供類別為基的構造，且 ECMAScript 第六版，提供「類」作為原有的原型架構之上的語法糖，提供建構物件與處理繼承時的另一種語法。

## 支援基於原型編程的語言

## 參照

## 參考來源

* James Noble (ed.), Antero Taivalsaari (ed.), Ivan Moore (ed.) (編). Prototype-Based Programming: Concepts, Languages and Applications. Springer-Verlag. 1999. ISBN 981-4021-25-3.
* Abadi, Martin; Luca Cardelli. A Theory of Objects. Springer-Verlag. ISBN 0-387-94775-2.

## 外部連結

* Class Warfare: Classes vs. Prototypes （頁面存檔備份，存於網際網路檔案館）, by Brian Foote
* Essential Object Oriented JavaScript （頁面存檔備份，存於網際網路檔案館）, by Brian O'Dell