基於原型程式設計

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

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

與基於類程式設計的比較

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

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

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

物件構造

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

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

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

// JavaScript 中真實的原型繼承樣式的例子。

// 使用文字物件記號 建立的 “無中生有” 物件。

var foo = {name: "foo", one: 1, two: 2};

// 另一個 “無中生有” 物件。

var bar = {three: 3};

// Gecko 和 Webkit JavaScript 引擎可以直接的操縱內部的原型連結。

// 為了簡單起見，我們假裝下面幾行程式碼可以工作而不考慮使用的引擎：

bar.proto = foo; //foo 現在是 bar 的原型。

// 如果我們嘗試從 bar 訪問 foo 的屬性，從此以後會成功。

bar.one// 解析為 1。

// 子物件的屬性也是可訪問的。

bar.three// 解析為 3。

// 自身的屬性遮蔽原型屬性。

bar.name = "bar";

foo.name; // 無影響，解析為 "foo"。

bar.name; // 解析為 "bar"。

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

var foo = {one: 1, two: 2};

// 等價於上例的 bar. prototype = foo

var bar = Object.create (foo);

bar.three = 3;

bar.one; // 1

bar.two; // 2

bar.three; // 3

委託

在使用委託的基於原型的語言中，語言執行時能夠分派正確的方法或找到正確的資料，只需遵循一系列委託指標（從物件到其原型）直到找到匹配項。在物件之間建立這種行為共享所需的只是委託指標。與基於類的面嚮物件語言中的類和例項之間的關係不同，原型與其分支之間的關係不要求子物件在此連結之外與原型具有記憶體或結構相似性。因此，子物件可以隨著時間的推移繼續被修改和修正，而無需像在基於類的系統中那樣重新安排其相關原型的結構。要注意，同樣重要的是，不僅可以新增或更改資料，還可以新增或更改方法。出於這個原因，一些基於原型的語言將資料和方法都稱為 “槽”（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 第六版，提供 “類” 作為原有的原型架構之上的語法糖，提供建構物件與處理繼承時的另一種語法。

支援基於原型程式設計的語言

: ABCL/1, ABCL/R, ABCL/R2, ABCL/c+
AutoHotkey
ColdC
ECMAScript/JavaScript
Falcon
Io
Ioke
Lisaac
LPC
Lua
NewtonScript
Nix
Omega
Perl, 透過 Class::Prototyped 模組
R, 透過 proto 包
REBOL
Self
Seph
Tcl，透過 snit 擴充套件