發(fā)布于:2021-01-19 09:56:45
0
105
0
在停滯了幾年之后,我們最近看到JavaScript語言開發(fā)取得了相當(dāng)大的進(jìn)展。ECMAScript5標(biāo)準(zhǔn)現(xiàn)在已經(jīng)有3年的歷史了,所有主要的瀏覽器供應(yīng)商都在其當(dāng)前版本中支持它。因此,現(xiàn)在也許正是真正接受新的語言特征并采用現(xiàn)代發(fā)展風(fēng)格的時(shí)候。
如果JS不僅僅用于jQuery一行程序,那么您將需要一種結(jié)構(gòu)化的方法。有很多方法可以用JS來構(gòu)造代碼。一個(gè)偉大的也是非常常見的方法是使用對(duì)象和原型繼承。
假設(shè)你建立了一個(gè)商店,你需要快遞產(chǎn)品。在以前的日子里,在ECMAScript5(ES5)之前,您可能會(huì)編寫如下內(nèi)容:
var Product = function(name) {
this.name = name;
};
Product.prototype.showName = function() {alert(this.name);};
var myProduct = new Product("Super Fancy TV");
myProduct.showName();
實(shí)際上,這還不錯(cuò)。您有一個(gè)構(gòu)造函數(shù)來創(chuàng)建對(duì)象并將方法附加到原型。所有單個(gè)產(chǎn)品都有這些方法,但有幾個(gè)弱點(diǎn)需要考慮:
在JavaScript語言中沒有私有屬性。name的值可以隨時(shí)從外部更改。
方法是分散的。即使你把它們放在一個(gè)地方,也沒有一個(gè)單一的結(jié)構(gòu)來定義你的對(duì)象概念——就像許多其他面向?qū)ο笳Z言中的類一樣。
你很容易忘記使用“new”關(guān)鍵字。這不會(huì)引發(fā)錯(cuò)誤,它只會(huì)導(dǎo)致完全不同的行為,可能會(huì)導(dǎo)致一些很難找到的真正討厭的bug。
繼承將帶來更多問題。在JS社區(qū)中,沒有一個(gè)一致同意的方法來正確地執(zhí)行它。
由于這些原因,許多開發(fā)人員已經(jīng)創(chuàng)建了提供所有類型的對(duì)象創(chuàng)建和實(shí)例化邏輯的庫、框架和工具。其中許多引入了類(例如Prototype或Coffescript)。
如果你愿意,你可以走這條路,但我不推薦??赡芎茈y相信,但原型繼承實(shí)際上比基于類的繼承容易得多,甚至提供了額外的好處??纯雌渌脑驼Z言,比如IO或Self。正是舊的JavaScript語法使得原型難以使用
道格拉斯·克羅克福德開創(chuàng)了一種更好的方法。他寫了一篇短文對(duì)象.創(chuàng)建
方法已進(jìn)入ES5標(biāo)準(zhǔn)-以稍微擴(kuò)展的形式
好消息是,您不需要使用帶有ES5實(shí)現(xiàn)的現(xiàn)代瀏覽器。Mozilla開發(fā)者網(wǎng)絡(luò)有一個(gè)polyfill。它允許您使用創(chuàng)建對(duì)象的新方法,即使在IE8及以下的舊瀏覽器中也是如此。這一點(diǎn)很重要,因?yàn)镮E7/IE8的市場份額總和仍約為9%。如果只針對(duì)Firefox、Chrome、Safari或Opera用戶,這不是問題。
if (!Object.create) {
Object.create = function (o) {
if (arguments.length > 1) {
throw new Error('Object.create implementation only accepts the first parameter.');
}
function F() {}
F.prototype = o;
return new F();
};
}
只需包括從上面的polyfill你將有一個(gè)對(duì)象.創(chuàng)建功能。代碼段的第一行檢查對(duì)象.創(chuàng)建已經(jīng)有了。這樣可以確保在當(dāng)前瀏覽器提供本機(jī)實(shí)現(xiàn)的情況下,polyfill不會(huì)覆蓋本機(jī)實(shí)現(xiàn)。如果您需要其他ES5功能的額外多填充,可以使用Kris Kowal的ES5填充。
讓我們再看一看商店項(xiàng)目。如果您只想創(chuàng)建一個(gè)產(chǎn)品,則不需要對(duì)象.創(chuàng)建. 直接創(chuàng)建對(duì)象即可。
var myProduct = {
price: 399.50,
name: 'Super Fancy TV'
};
然而,這并不是最好的選擇。您可以從外部輕松地操縱對(duì)象的屬性,并且無法檢查或轉(zhuǎn)換指定的值??紤]像這樣的任務(wù)我的產(chǎn)品價(jià)格=-20。
像這樣的一個(gè)錯(cuò)誤將確保你的公司銷售很多產(chǎn)品很快,并有真正高興的客戶-客戶收到20美元每購買。你的公司做不了多久!
多年的面向?qū)ο缶幊毯驮O(shè)計(jì)經(jīng)驗(yàn)告訴我們,要將對(duì)象的內(nèi)部狀態(tài)與其外部接口分離開來。通常需要將屬性私有化,并提供一些getter和setter方法。
ES5為使用getter和setter訪問屬性提供了一個(gè)很好的概念。不幸的是,沒有辦法讓它們在舊瀏覽器中運(yùn)行——你不能只使用polyfill。所以在接下來的兩年左右,我們大多數(shù)人都沒有奢侈的使用它。同時(shí),您可以使用許多方法(參見Stoyan Stefanov的JavaScript模式)。它們都有自己獨(dú)特的優(yōu)點(diǎn)和缺點(diǎn)。沒有單一的解決方案,所以歸根結(jié)底是口味的問題。以下是我通常做的:
帶有下劃線的前綴屬性,例如價(jià)格。這只是將屬性標(biāo)記為private的約定。永遠(yuǎn)不要從對(duì)象外部調(diào)用它們;違反此規(guī)則通常很容易發(fā)現(xiàn)。還有更復(fù)雜的方法使用基于閉包的模式來存檔真實(shí)的隱私(例如Stefanov的書)。我的觀點(diǎn)是,大多數(shù)情況下,它們不值得付出努力。
提供一個(gè)以“set”開頭的setter方法,例如setPrice(value)。在舊瀏覽器中,無法覆蓋賦值運(yùn)算符=。所以這是下一個(gè)最好的選擇。使用java和C++程序員。
有時(shí)屬性可能只供內(nèi)部使用—或者您希望它只準(zhǔn)備就緒。在這種情況下,只需省略適當(dāng)?shù)姆椒āR虼烁玫膶?shí)現(xiàn)可能是這樣的。
var myProduct={u price:99.50,u name:'Grindle 3',
price: function() {return this._price;},
name: function() {return this._name;},
setPrice: function(p) {this._price = p;},
};
如果您想在設(shè)置價(jià)格之前檢查價(jià)格,現(xiàn)在可以輕松地執(zhí)行此操作。
setPrice: function(p) {
if (p <= 0) {
throw new Error("Price must be positive");
}
this._price = p;
}
真正的ECMAScript5實(shí)現(xiàn)甚至更好。它還有一個(gè)額外的好處,就是隱式地調(diào)用getter和setter。我的產(chǎn)品價(jià)格= 85.99. 最后,JavaScript支持統(tǒng)一訪問原則!
var myProduct = {
...
get price() {return this._price;},
set price(p) {
if (p <= 0) { throw new Error("Price must be positive"); }
this._price = p;
},
...
一家真正的商店當(dāng)然會(huì)有很多產(chǎn)品。你不想從頭開始創(chuàng)造它們。你需要一個(gè)地方,在那里你可以放置共同的結(jié)構(gòu)和行為。JS中通常的模式是為此創(chuàng)建一個(gè)父對(duì)象,一個(gè)產(chǎn)品的原型,所有其他產(chǎn)品都是從這個(gè)原型派生的。與對(duì)象.創(chuàng)建你用它來制造新產(chǎn)品。
var Product = {
_price: 0,
_name: '',
price: function() {return this._price;},
name: function() {return this._name;},
setPrice: function(p) {this._price = p;},
setName: function(n) {this._name = n;}
};
var product1 = Object.create(Product);
product1.setName('Grindle 3');
product1.setPrice(99.50);
為產(chǎn)品的所有新實(shí)例調(diào)用所有必要的設(shè)置程序可能有點(diǎn)不方便。為了避免這種情況,提供一個(gè)initialize方法,類似于其他語言的構(gòu)造函數(shù)。
var Product = {
...
init: function(name, price) {
this._name = name;
this._price = price;
},
...
};
var product1 = Object.create(Product).init('Grindle 3', 99.50);
原型方法的一大優(yōu)點(diǎn)是實(shí)例化和繼承的統(tǒng)一。你不需要任何特別的東西-只要使用對(duì)象.創(chuàng)建為了繼承。
var Product = {
...
};
var Book = Object.create(Product);
Book._author = null;
Book._numPages = null;
Book.setAuthor = function(author) {this._author = author;};
Book.setNumPages = function(num_pages) {this._numPages = num_pages;};
Book.author = function() {return this.author();};
Book.numPages = function() {return this.numPages();};
書是從產(chǎn)品中衍生出來的新事物。它沒有設(shè)置特定的值,比如名稱和價(jià)格,而是獲得額外的結(jié)構(gòu)和行為。新的屬性author和numPages組成了這個(gè)結(jié)構(gòu),而它們的簡單getter和setter充當(dāng)了一些實(shí)際行為的占位符示例。
缺點(diǎn)是多次調(diào)用Book是多余的,而且整個(gè)語法與定義基本對(duì)象有很大的不同。因此,通常構(gòu)建一個(gè)小的extend函數(shù),使繼承更加方便。
Object.prototype.extend = function(newProperties) {
for (var propertyName in newProperties) {
this[propertyName] = newProperties[propertyName];
}
return this;
};
再說一次,在真正的ES5中你不需要這個(gè)。真實(shí)的對(duì)象.創(chuàng)建允許包含擴(kuò)展名的第二個(gè)參數(shù)。
現(xiàn)在可以使用新的extend方法來分解代碼。
var Product = {
...
};
var Book = Object.create(Product).extend({
_author: null,
_numPages: null,
setAuthor: function(author) {this._author = author;},
setNumPages: function(num_pages) {this._numPages = num_pages;},
author: function() {return this.author();},
numPages: function() {return this.numPages();}
});
與基于靜態(tài)類的方法相比,對(duì)象和原型概念有許多優(yōu)點(diǎn),例如:
繼承和實(shí)例化的統(tǒng)一。你可以用同樣的機(jī)制(對(duì)象.創(chuàng)建)從原型繼承或從原型生成實(shí)例(類似于類的用法)。實(shí)際上,這是一回事。
價(jià)值觀的繼承。您可以從原型繼承值;無需在構(gòu)造函數(shù)中設(shè)置默認(rèn)值。
原型的運(yùn)行時(shí)修改。在JS中,運(yùn)行時(shí)和編譯時(shí)沒有區(qū)別。您可以在程序執(zhí)行期間修改原型,而更多的靜態(tài)語言只允許在編譯器運(yùn)行之前更改類。這不是原型繼承的直接優(yōu)勢,而是JS的動(dòng)態(tài)特性。甚至還有基于類的語言,允許對(duì)類進(jìn)行運(yùn)行時(shí)修改——Ruby或Smalltalk就可以了。但是JavaScript的純對(duì)象方法使這變得簡單多了——如果您喜歡進(jìn)行元編程,這將是一個(gè)真正的好處。
對(duì)象.創(chuàng)建提供了一種更具吸引力的方法來使用原型,但仍然有一些怪癖。您必須實(shí)現(xiàn)自己的getter/setter、init和extend方法,才能在較舊的瀏覽器上工作。這些缺點(diǎn)最終會(huì)消失,當(dāng)你放棄墊片和蓬勃發(fā)展的真正ECMAScript5-你已經(jīng)可以這樣做,如果IE8和以下是不關(guān)心你。在此之前,您可以使用對(duì)象.創(chuàng)建. 它可能比你想象的更快成為標(biāo)準(zhǔn)方法。
Marco Emrich于1993年開始專業(yè)軟件開發(fā),并使用了許多不同的語言和技術(shù)。他擁有計(jì)算機(jī)科學(xué)(德語)學(xué)位。并為弗勞恩霍夫IESE研究所做了一些關(guān)于生成編程的研究。目前,他受雇于德國IT培訓(xùn)中心“網(wǎng)站管理員akademie”。在那里,他擔(dān)任作家、培訓(xùn)師、軟件開發(fā)人員和項(xiàng)目經(jīng)理。他也喜歡為了好玩而編寫代碼,比如業(yè)余時(shí)間的項(xiàng)目幻想-卡片.net是的。
作者介紹