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