基於原型編程

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

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

## 與基於類編程的比較

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

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

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

## 對象構造

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

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

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

```javascript
// 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兼容性表格）

```javascript
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+
- Agora (程式語言)
- AutoHotkey
- Cecil (程式語言)
- ColdC
- ECMAScript/JavaScript
- Etoys (程式語言)
- Falcon
- Io
- Ioke
- Lisaac
- Logtalk
- LPC
- Lua
- MOO
- NekoVM
- NewtonScript
- Nix
- Obliq
- Object Lisp
- Omega
- OpenLaszlo
- Perl, 通過Class::Prototyped模塊
- R, 通過proto包
- REBOL
- Self
- Seph
- SmartFrog
- TADS
- Tcl，通過snit擴展

## 外部連結

- Class Warfare: Classes vs. Prototypes , by Brian Foote
- Essential Object Oriented JavaScript , by Brian O'Dell