中文字幕一区二区人妻电影,亚洲av无码一区二区乱子伦as ,亚洲精品无码永久在线观看,亚洲成aⅴ人片久青草影院按摩,亚洲黑人巨大videos

JavaScript的行為驅(qū)動開發(fā):第二部分

發(fā)布于:2021-02-12 00:00:44

0

82

0

JavaScript 驅(qū)動開發(fā) TDD

本文是由兩部分組成的系列文章中的第二篇。為了快速起步,您可以去我的博客里找到第一部分。 

JavaScript是面向?qū)ο蟮?/strong>

JS是一種面向?qū)ο蟮恼Z言。這不同于面向類。如果您具有C ++或Java之類的工業(yè)語言背景,您可能會認為JS缺少某些東西。我也這么想了很久。

實際上,諸如C ++或Java之類的流行語言都沒有強大的OOP概念實現(xiàn),它們是從強大的Smalltalk繼承而來的。OOP的發(fā)展甚至沒有停止Smalltalk。有許多語言使Smalltalk中的概念更進一步。這些概念僅在學術(shù)界廣泛使用。

一種改進是擺脫類。諸如Cecil,Self或最近的IO之類的語言都嘗試了一種原型方法,該方法也被引入JS。

您可能仍然認為,與諸如Self之類的語言相比,JS沒有最佳的原型實現(xiàn)方式和丑陋的語法。但是,原型繼承概念本身是一個很大的改進。 

舊的方式

創(chuàng)建對象的舊方法是提供構(gòu)造函數(shù)。這是針對Java和C ++開發(fā)人員的,旨在為他們提供他們習慣的編碼樣式。但是構(gòu)造函數(shù)也有缺點。因此ECMAScript5引入了一種更好的創(chuàng)建對象的方法。

在ECMAScript5介入之前,它看起來如何?假設您已經(jīng)建立了一家商店,現(xiàn)在您需要開始在線銷售各種產(chǎn)品。在過去,您可能會這樣寫:

var Product = function(name) { this.name = name; }; Product.prototype.showName = function() {alert(this.name);}; var myProduct = new Product("Super Fancy TV"); myProduct.showName();<span> </span>

構(gòu)造函數(shù)(javascript_refresher / product_in_ecma3.js)

實際上,這還不錯。您有一個構(gòu)造函數(shù)來創(chuàng)建對象并將方法附加到原型。所有單個產(chǎn)品都有這些方法,但是有幾個缺點需要考慮。 

  • JS語言中沒有私有屬性 。名稱的值可以隨時從外部更改。

  • 該 方法 被分散。 即使將它們?nèi)糠旁谝粋€地方,也沒有單一的結(jié)構(gòu) 來定義對象概念–就像許多其他 面向?qū)ο笳Z言中的類一樣。

  • 您 很容易 忘記 使用“ new”關(guān)鍵字。 這不會引發(fā)錯誤,只會導致完全不同的 行為,并且可能會導致一些很難發(fā)現(xiàn)的非常討厭的錯誤。

  • 繼承 會 帶來更多問題。在 JS社區(qū)中,沒有一種正確執(zhí)行方法的共識方法。

由于這些問題,許多開發(fā)人員創(chuàng)建了提供所有類型的對象創(chuàng)建和實例化邏輯的庫,框架和工具。他們中的許多人介紹了類(例如Prototype5或Coffeescript6)。

您可以根據(jù)需要選擇此路徑,但我不建議這樣做。也許難以置信,但是原型繼承實際上比基于類的繼承容易得多,甚至還提供了其他好處。只需看看其他原型語言,例如IO或Self。正是舊的JS語法使原型難以使用。

道格拉斯·克羅克福德(Douglas Crockford)率先提出了一種更好的方法。他編寫了一個簡短的Object.create方法,該方法以稍微擴展的形式進入了ES5標準。 

對象創(chuàng)建

好消息是,您不需要具有ES5實現(xiàn)的現(xiàn)代瀏覽器。Mozilla開發(fā)人員網(wǎng)絡(MDN)提供了一個polyfill。polyfill是一種使較新的瀏覽器可以使用現(xiàn)代功能的技術(shù)。您可以在Modernizr-Wiki上找到其他polyfills。

MDN的Object.create-polyfill允許您使用在舊版瀏覽器(例如IE8)中創(chuàng)建對象的新方法。當您認為IE7 / IE8的綜合市場份額仍約為7%(2013年1月)時,這一點很重要。如果您僅針對IE9 +,F(xiàn)irefox,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(); }; }<span style="font-family: Verdana, Arial, Helvetica, sans-serif;font-size: 12px"> </span>

只需包含清單1.6中的polyfill,以確保您具有Object.create函數(shù)。片段的第一行檢查Object.create是否已經(jīng)存在。這樣可以確保如果當前瀏覽器提供的方法,polyfill將不會覆蓋本機實現(xiàn)。如果您需要用于其他ES5功能的附加填充料,則可以使用Kris Kowal的或David de Rosier的es5-shim。 

獨奏對象

讓我們再看看shop項目。如果您只想創(chuàng)建一種產(chǎn)品,則不需要Object.create。只需直接創(chuàng)建對象:

var myProduct = { price: 99.50, name: 'Grindle 3' };

但是,這不是最佳選擇。您可以從外部輕松地操作對象的屬性,但是沒有辦法檢查或轉(zhuǎn)換分配的值??匆幌逻@個作業(yè): 

MyProduct.price = -20;

像這樣的錯誤將意味著您的公司快速銷售許多產(chǎn)品,并擁有非常滿意的客戶-每次購買可賺取20美元的客戶。您的公司將無法長期這樣做!

多年的面向?qū)ο缶幊毯驮O計經(jīng)驗教會我們將對象的內(nèi)部狀態(tài)與它的外部接口分開。您通常希望將屬性設為私有,并提供一些getter和setter方法。

Getters and setters

ES5提供了一個使用getter和setter訪問屬性的絕佳概念。不幸的消息是,沒有辦法讓它們在較舊的瀏覽器中運行–您不能僅使用polyfill。因此,在接下來的兩年左右的時間里,我們大多數(shù)人都沒有奢侈地使用它。同時,您可以使用多種方法(請參閱[Stefanov 2010])。它們都有各自獨特的優(yōu)點和缺點。沒有單一的解決方案,因此歸結(jié)為一個問題。這是我通常的工作:

帶下劃線的前綴屬性,例如_price。這只是將屬性標記為私有的約定。您永遠不要從對象外部調(diào)用它們。通常容易發(fā)現(xiàn)違反此規(guī)則的情況。還有一些更復雜的方法,使用基于閉包的模式來歸檔實際隱私(例如,在[Stefanov 2010]中)。我的意見是,大多數(shù)時候他們不值得付出努力。

提供以“ set”開頭的setter方法,例如setPrice(value)。在較舊的瀏覽器中,無法覆蓋賦值運算符=。所以這是下一件好事。Java和C ++程序員已經(jīng)習慣了。

提供具有原始屬性名稱的getter方法,例如price()。許多程序員更喜歡在方法的前面加上“ get”。我認為這在JS中不是必需的,只會使代碼的可讀性降低–您的工作量可能會有所不同。 有時,一個屬性可能只供內(nèi)部使用,或者您希望它僅準備就緒。在這種情況下,只需忽略適當?shù)姆椒纯?。因此,清?.8顯示了更好的實現(xiàn)。

var myProduct = { _price: 99.50, _name: 'Grindle 3', price: function() {return this._price;}, name: function() {return this._name;}, setPrice: function(p) {this._price = p;}, };<span style="font-family: Verdana, Arial, Helvetica, sans-serif;font-size: 12px"> </span>

具有g(shù)etter和setter的獨奏對象(javascript_refresher / product_with_getters_and_setters.js)。 如果要在設置之前檢查價格,現(xiàn)在可以輕松執(zhí)行以下操作:

setPrice: function(p) { if (p <= 0) { throw new Error("Price must be positive"); } this._price = p; }<span style="font-family: Verdana, Arial, Helvetica, sans-serif;font-size: 12px"> </span>

帶有檢查的setPrice方法(javascript_refresher / product_with_price_check.js)。

真正的ECMAScript5實現(xiàn)更好(清單1.10)。它具有隱式調(diào)用getter和setter的附加好處,例如myProduct.price = 85.99。最后,JS支持統(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; }, ...<span style="font-family: Verdana, Arial, Helvetica, sans-serif;font-size: 12px"> </span>

樣機

一個真正的商店將有很多產(chǎn)品,您不想從頭開始創(chuàng)建所有產(chǎn)品。您將需要一個可以放置通用結(jié)構(gòu)和行為的地方。JS中通常的模式是為此創(chuàng)建一個父對象-產(chǎn)品的原型,所有其他產(chǎn)品均來自該產(chǎn)品。使用Object.create可以從中構(gòu)建新產(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); var product2 = Object.create(Product); product2.setName('yPhone 7'); product2.setPrice(599.99);

也有慣例以此類首字母開頭這樣的原型,例如更傳統(tǒng)的語言中的類(即var Product而不是var product)。

初始化器

為了將新產(chǎn)品對象的所有屬性設置為正確的值,您需要調(diào)用其所有setter方法-這是一個很大的麻煩。您應該改為構(gòu)建一個初始化方法??梢詫⑦@種方法與其他語言中的構(gòu)造方法進行比較。

var Product = { ... init: function(name, price) { this._name = name; this._price = price; return this; }, ... }; var aProduct = Object.create(Product).init('Grindle 3', 99.50);

更好的是,覆蓋產(chǎn)品的create -Method來封裝創(chuàng)建和初始化。

var Product = { ... create: function(name, price) { return Object.create(this).init(name, price); }, ... }; var aProduct = Product.create('Grindle 3', 99.50);

原型方法的一大優(yōu)勢是實例化和繼承的統(tǒng)一。您不需要任何特殊的操作,也可以只使用Object.create進行繼承。

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();};

Book是從Product派生的新對象。它無需設置特定的值(如 名稱和價格),而是通過getter和setter來獲得其他結(jié)構(gòu)和行為(作者頁面和num Pages)。

缺點是 多次調(diào)用Book是多余的,整個語法與定義基礎對象完全不同。因此,您通常會構(gòu)建一個小的 擴展函數(shù),使繼承更加方便(清單1.15)。同樣,您在真正的ES5中將不需要此功能。真正的Object.create允許包含擴展名的第二個參數(shù)。

提示:jQuery(http://api.jquery.com/jQuery.extend)和Underscore.js(http://underscorejs.org/#extend)中提供了Object.extend的替代實現(xiàn) 。如果您的項目中已經(jīng)有這些庫之一,則可以改用它們。

Object.prototype.extend = function(props) { for (var prop in props) { this[prop] = prop; } return this; };

現(xiàn)在,您可以使用新的extend方法重構(gòu)代碼。

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 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();} }); console.log(Product); console.log(Book);

內(nèi)部原型繼承

與基于靜態(tài)類的方法相比,對象和原型概念具有許多優(yōu)點,例如:

  • 繼承和實例的統(tǒng)一。您可以使用相同的機制(Object.create)從原型繼承或從原型構(gòu)建實例(類似于類用法)。實際上–這是同一回事。

  • 價值的繼承。您可以從原型繼承值;不需要在構(gòu)造函數(shù)中設置默認值。

  • 原型的運行時修改。在JS中,運行時和編譯時沒有區(qū)別。您可以在程序執(zhí)行期間修改原型,而更多的靜態(tài)語言僅允許在編譯器運行之前更改類。這不是原型繼承的直接優(yōu)勢,而是JS的動態(tài)特性。甚至有基于類的語言可供使用,這些類允許對類進行運行時修改-Ruby或Smalltalk可以滿足要求。但是JavaScript的僅對象方法使這一過程變得簡單得多。如果您想進行任何元編程,這將是一大收獲。

“僅對象”方法的最大優(yōu)點是其簡單性。讓我們看一下內(nèi)部工作原理:首先,您不需要對方法進行任何特殊處理。方法只是碰巧包含函數(shù)的對象屬性。因此,查找簡單數(shù)字或調(diào)用方法都沒關(guān)系?,F(xiàn)在看一下JS如何確定要調(diào)用的方法。清單1.17演示了該原理。

var Product = { init: function(name) { this._name = name; return this; }, _name: '', name: function() { return this._name; }, setName: function(n) { this._name = n; } }; var Book = Object.create(Product).extend({ init: function(name, author) { Product.init(name); this._author = author; return this; }, author: null, setAuthor: function(author) { this._author = author; }, author: function() { return this.author(); } }); var myBook = Object.create(Book).init('Lords of the Rings', 'J.R.R. Tolkien'); myBook.mostImportantHobbit = "Frodo";<span style="font-family: Verdana, Arial, Helvetica, sans-serif;font-size: 12px"> </span>

如果您嘗試獲取myBook.mostImortantHobbit的值 ,則JavaScript引擎僅查看myBook對象并返回該值。

查找myBook.name()需要更多步驟。JS引擎在myBook對象上找不到名稱-屬性 ,因此需要在其原型Book中進行查找 。它也不存在,因此它會跟蹤原型鏈,直到找到它。該屬性名稱 實際上在Book的原型 產(chǎn)品中可用。因此,JS解釋了括號并調(diào)用了name中包含的函數(shù)。該函數(shù)在myBook的上下文中執(zhí)行。因此,this._name 指的是“指環(huán)王””。即使JS確實需要執(zhí)行幾個步驟,它們也很容易理解。始終遵循原型鏈。

實例化和繼承不需要區(qū)別對待。

其他要考慮的事情

在實際的項目中,還需要考慮許多其他事項。您通常希望將代碼保留在名稱空間中。要管理名稱空間和文件依賴性,您可能需要使用提供AMD(異步模塊定義)的工具。 RequireJS 或 curl 是流行的。

您甚至可能希望使用更大的基礎框架之一,例如 Ember, Backbone 或 AngularJS。在這里我不會深入研究這些東西,因為它們對于理解行為驅(qū)動的開發(fā)不是必需的。我敦促您真正研究構(gòu)建代碼庫的更好方法。它可以帶來很大的不同。

替代樣式

JS是一種非常靈活的語言,支持各種編程范例(至少是功能和面向?qū)ο蟮模?。這為軟件設計和開發(fā)提供了許多不同的方法。您可能會喜歡純函數(shù)式方法,或者對原型進行OOP,或者將庫用于基于類的面向?qū)ο?。您可能會考慮使用混合/特征或其他高級構(gòu)造。也許您會考慮使用像Harmonizr這樣的預處理器/編譯器來編寫ECMAScript6 / Harmony代碼,甚至嘗試使用CoffeeScript。就本教程而言,沒關(guān)系。行為驅(qū)動方法應使用這些特征或混合中的任何一種起作用。

因此,在這里加點鹽就可以成為我的JS風格;與實際項目相比,我將其簡化了一些??梢詫⑺鼛胄袨轵?qū)動開發(fā)的美好世界。