Javascript 是最低級(jí)的Web 編程接口,隨處可見。隨著Web 日益成為日常生活的一部分,Javascript 也開始變得備受關(guān)注。Javascript 是一個(gè)經(jīng)常遭到誤解的語言,被認(rèn)為是一種玩具語言或者一種“不成熟的Java™ 語言”。Javascript 最飽受非議的特性之一是它的原型對(duì)象系統(tǒng)。盡管不可否認(rèn)Javascript 是存在一些缺陷,但原型對(duì)象系統(tǒng)并不在其內(nèi)。在本文中,我們將了解功能強(qiáng)大、簡(jiǎn)潔、典雅的Javascript 原型的面向?qū)ο缶幊獭?/p>
對(duì)象的世界
當(dāng)您開始新的一天時(shí)(開車去上班,坐在辦公桌前執(zhí)行一個(gè)任務(wù),吃一頓飯,逛逛公園),您通常可以掌控您的世界,或者與之交互,不必了解支配它的具體物理法則。您可以將每天面對(duì)的各種系統(tǒng)看作是一個(gè)單元,或者是一個(gè)對(duì)象。不必考慮它們的復(fù)雜性,只需關(guān)注您與它們之間的交互。
歷史
Simula 是一種建模語言,通常被認(rèn)為是第一個(gè)面向?qū)ο?Object-oriented, OO) 的語言,隨后出現(xiàn)的此類語言包括Smalltalk、C++、Java 和C#。那時(shí),大多數(shù)面向?qū)ο蟮恼Z言是通過類來定義的。后來,Self 編程語言(一個(gè)類似Smalltalk 的系統(tǒng))開發(fā)人員創(chuàng)建了一種可替代的輕量級(jí)方法來定義這類對(duì)象,并將這種方法稱為基于原型的面向?qū)ο缶幊袒蛘咴蛯?duì)象編程。
終于,使用一種基于原型的對(duì)象系統(tǒng)將Javascript 開發(fā)了出來,Javascript 的流行將基于原型的對(duì)象帶入了主流。盡管許多開發(fā)人員對(duì)此很反感,不過仔細(xì)研究基于原型的系統(tǒng),就會(huì)發(fā)現(xiàn)它的很多優(yōu)點(diǎn)。
面向?qū)ο蟮木幊?Object-oriented, OO) 試圖創(chuàng)建工作原理相似的軟件系統(tǒng),面向?qū)ο缶幊淌且粋(gè)功能強(qiáng)大的、廣泛流行的、用于軟件開發(fā)的建模工具。 面向?qū)ο缶幊讨粤餍校且驗(yàn)樗从沉宋覀冇^察世界的方法:將世界看作是一個(gè)對(duì)象集合,可與其他對(duì)象進(jìn)行交互,并且可以采用各種方式對(duì)其進(jìn)行操作。面向?qū)ο缶幊痰膹?qiáng)大之處在于其兩個(gè)核心原則:
封裝允許開發(fā)人員隱藏?cái)?shù)據(jù)結(jié)構(gòu)的內(nèi)部工作原理,呈現(xiàn)可靠的編程接口,使用這些編程接口來創(chuàng)建模塊化的、適應(yīng)性強(qiáng)的軟件。我們可以將信息封裝視為信息隱藏。
繼承增強(qiáng)封裝功能,允許對(duì)象繼承其他對(duì)象的封裝行為。我們可以將信息繼承視為是信息共享。
這些原則對(duì)于大多數(shù)開發(fā)人員來說是眾所周知的,因?yàn)槊總(gè)主流編程語言都支持面向?qū)ο缶幊蹋ㄔ诤芏嗲闆r下是強(qiáng)制執(zhí)行的)。盡管所有面向?qū)ο笳Z言都以這樣或那樣的形式支持這兩個(gè)核心原則,但多年來至少形成了2 種定義對(duì)象的不同方法。
在本文中,我們將了解原型對(duì)象編程和Javascript 對(duì)象模式的優(yōu)勢(shì)。
什么是Prototypo?類和原型的關(guān)系
類提供對(duì)象的抽象定義,為整個(gè)類或?qū)ο蠹隙x了共享的數(shù)據(jù)結(jié)構(gòu)和方法。每個(gè)對(duì)象都被定義為其類的一個(gè)實(shí)例。類還有根據(jù)其定義和(可選)用戶參數(shù)來構(gòu)造類對(duì)象的責(zé)任。
一個(gè)典型的示例是Point 類及其子類Point3D,用來分別定義二維點(diǎn)和三維點(diǎn)。清單1 顯示了Java 代碼中的類。
清單1. Java Point 類
class Point {
private int x;
private int y;
static Point(int x, int y) {
this.x = x;
this.y = y;
}
int getX() {
return this.x;
}
int getY() {
return this.y;
}
void setX(int val) {
this.x = val;
}
void setY(int val) {
this.y = val;
}
}
Point p1 = new Point(0, 0);
p1.getX() // => 0;
p1.getY() // => 0;
// The Point3D class 'extends' Point, inheriting its behavior
class Point3D extends Point {
private int z;
static Point3D(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
int getZ() {
return Z;
}
void setZ(int val) {
this.z = val;
}
}
Point3D p2 = Point3D(0, 0, 0);
p2.getX() // => 0
p2.getY() // => 0
p2.getZ() // => 0
和通過類來定義對(duì)象相比,原型對(duì)象系統(tǒng)支持一個(gè)更為直接的對(duì)象創(chuàng)建方法。例如,在Javascript 中,一個(gè)對(duì)象是一個(gè)簡(jiǎn)單的屬性列表。每個(gè)對(duì)象包含另一個(gè)父類或原型的一個(gè)特別引用,對(duì)象從父類或原型中繼承行為。您可以使用 Javascript 模擬Point 示例,如清單2 所示。
清單2. Javascript Point 類
var point = {
x : 0,
y : 0
};
point.x // => 0
point.y // => 0
// creates a new object with point as its prototype, inheriting point's behavior
point3D = Object.create(point);
point3D.z = 0;
point3D.x // => 0
point3D.y // => 0
point3D.z // => 0
傳統(tǒng)對(duì)象系統(tǒng)和原型對(duì)象系統(tǒng)有本質(zhì)的區(qū)別。傳統(tǒng)對(duì)象被抽象地定義為概念組的一部分,從對(duì)象的其他類或組中繼承一些特性。相反,原型對(duì)象被具體地定義為特定對(duì)象,從其他特定對(duì)象中繼承行為。
因此,基于類的面向?qū)ο笳Z言具有雙重特性,至少需要2 個(gè)基礎(chǔ)結(jié)構(gòu):類和對(duì)象。由于這種雙重性,隨著基于類的軟件的發(fā)展,復(fù)雜的類層次結(jié)構(gòu)繼承也將逐漸開發(fā)出來。通常無法預(yù)測(cè)出未來類需要使用的方法,因此,類層次結(jié)構(gòu)需要不斷重構(gòu),讓更改變得更輕松。
基于原型的語言會(huì)減少上述雙重性需求,促進(jìn)對(duì)象的直接創(chuàng)建和操作。如果沒有通過類來束縛對(duì)象,則會(huì)創(chuàng)建更為松散的類系統(tǒng),這有助于維護(hù)模塊性并減少重構(gòu)需求。
直接定義對(duì)象的能力將會(huì)加強(qiáng)和簡(jiǎn)化對(duì)象的創(chuàng)建和操作。例如,在清單2 中,僅用一行代碼即可聲明您的point 對(duì)象:var point = { x: 0, y: 0 };。僅使用這一行代碼,就可以獲得一個(gè)完整的工作對(duì)象,從Javascript Object.prototype(比如toString 方法)繼承行為。要擴(kuò)展對(duì)象行為,只需使用point 將另一個(gè)對(duì)象聲明為其原型。相反,即使最簡(jiǎn)潔的傳統(tǒng)面向?qū)ο笳Z言,也必須先定義一個(gè)類,然后在獲得可操作對(duì)象之前將其實(shí)例化。要繼承有關(guān)行為,可能需要定義另一個(gè)類來擴(kuò)展第一個(gè)類。
原型模式理論上比較簡(jiǎn)單。作為人類,我們往往習(xí)慣于從原型方面思考問題。例如,Steve Yegge 在博客文章“The Universal Design Pattern”(請(qǐng)參閱參考資料)中討論過,以橄欖球運(yùn)動(dòng)員Emmitt Smith 為例,誰擁有速度、敏捷性和剪力,誰就將成為美國國家橄欖球聯(lián)盟(National Football League,NFL)所有新成員的榜樣。當(dāng)一個(gè)新跑步運(yùn)動(dòng)員LT 發(fā)揮超常撿到球時(shí),評(píng)論員通常會(huì)這樣說:
“LT 有雙Emmitt 的腿。”
“他就像Emmitt 一樣自由穿過終點(diǎn)線。”
“他跑一英里只用5 分鐘!”
評(píng)論員以原型對(duì)象Emmitt Smith 為模型來評(píng)論新對(duì)象LT。在Javascript 中,這類模型看起來如清單3 所示。
清單3. Javascript 模型
var emmitt = {
// ... properties go here
};
var lt = Object.create(emmitt);
// ... add other properties directly to lt
您可以將該示例與經(jīng)典模型進(jìn)行比較,在經(jīng)典模型中您可能會(huì)定義一個(gè)繼承自FootballPlayer 類的RunningBack 類。LT 和Emmitt 可能是RunningBack 的實(shí)例。這些Java 代碼編寫的類看起來如清單4 所示。
清單4. 3 個(gè)Java 類
class FootballPlayer {
private string name;
private string team;
static void FootballPlayer() { }
string getName() {
return this.name;
}
string getTeam() {
return this.team;
}
void setName(string val) {
this.name = val;
}
void setTeam(string val) {
this.team = val;
}
}
class RunningBack extends FootballPlayer {
private bool offensiveTeam = true;
bool isOffesiveTeam() {
return this.offensiveTeam;
}
}
RunningBack emmitt = new RunningBack();
RunningBack lt = new RunningBack();
經(jīng)典模型通常伴隨著極大的概念上的負(fù)擔(dān),但是對(duì)類實(shí)例emmitt 和lt(您得到的原型模型),并沒有提供細(xì)小的控制。(公平地說,F(xiàn)ootballPlayer 類并不是100% 需要,這里提供它只是為了與下一個(gè)示例進(jìn)行比較 )。有時(shí),這項(xiàng)開銷是有益的,但通常都是一個(gè)包袱。