原文點擊這裡
靈活的js語言可以輕易的就完成截然相反的兩種編程模式–函數型程式設計和物件導向的程式設計。
js原生的支援將函數當做變數來處理。你可以將一個函數賦值給一個變數,然後把他傳給其他的變數。抽象的說,我們可以認為js的函數是一個特殊的變數:只不過他的形式是“函數”。
js原生的也提供對象。在js中,對象可以認為是一系列平面化的屬性值和方法。他們看起來像是一個資料字典,而不像你在其他諸如java、c++、c#中看到的對象那樣。在經典的物件導向編程中,類表示可以通過new操作符來產生一個執行個體的模板。但是在js中,沒有可以當做藍本來產生對象的。在js中,一個對象的藍本更像是一個資料字典。所以在js中,你可以建立對象,然後可以在對象中儲存資料。當然,js的對象也提供一定程度的物件導向的特性,比如封裝和繼承。
js開發越來越熱,兩種模式的優劣是什嗎?js對兩種模式都支援,但是問題的答案得你自己來弄清楚。它兩種都支援,但並不是某一種特別的合適。OOP的js架構和函數型的js都同時存在。
函數型程式設計簡介
函數型程式設計中,每個程式碼片段都是在”function”之上建立的,這與傳統的OOP建立在”class”之上是不同的。一個函數只是對輸入的展現的一種操作。一個函數只是接收一些輸入,然後返回一些輸出,其他的都是隱藏起來的。
在函數型程式設計中,你通過在一個函數中調用其他的函數來完成程式的輸入與輸出。這通常都沒有可以處理輸入、儲存資料以及一系列的狀態更新的那一層。函數就像一個數值一樣,可以作為參數傳遞給其他函數。
javascript與函數型程式設計
需要明確的一個事實是:js並不是像F#那樣真正的函數型程式設計語言,雖然他有一些函數型程式設計模式的特性。利用這些特性,你也可以用js做很好的工作。到今天為止,使用jQuery就是這種模式最廣泛的應用了。
匿名函數是函數型程式設計的精華之所在。匿名函數是lambda計算式的另一種形式,一種可以適配經流行的編碼風格。
123 |
function (x,y){ return x + y;} |
普通函數和匿名的函數的唯一區別就在於名字。在函數的上下文中,當你只是需要函數的傳回值作為參數來使用的時候,並不是非得要給他一個函數名。通過下面這個例子我們來看看:
123456789101112131415161718 |
// 一個用來計算和的函數var CalcTotal = function(x, y) { return x + y;}; // 增加稅率var AddTaxes = function(x) { return x * 1.2;}; // 最終的計算函數var CalcTotalPlusTaxes = function (fnCalcTotal, fnAddTaxes, x, y) { return fnAddTaxes(fnCalcTotal(x, y));}; // 執行var result = CalcTotalPlusTaxes(CalcTotal, AddTaxes, 40, 60);alert(result); |
需要注意的是,你也可以使用匿名函數,而不是非得通過一個中間變數來傳遞給最終的計算式。
123 |
alert( (function(x){return x * 1.2})(100);); |
將匿名函數看做一個變數,可以很輕鬆的將資料和特性合并在一起。這也給你了一個機會去實驗一些其他的設計原則。當高一級的模組(無論對象還是函數)需要低一級的模組提供的傳回值的時候,就可以將低一級的模組直接當做參數傳過去。很多情況下,這會提高函數參數的可讀性,並且讓代碼更優雅一些,當然,也會讓你在處理複雜事物的時候更加得心應手。
jQuery和函數型程式設計
jQuery在函數型程式設計方面吸引了人們極大的關注。整個jQuery庫都是基於jquery對象或者說$。$是DOM元素的一個封裝,DOM元素也可以通過$()運算式傳遞進去。另外,jQuery支援鏈式操作,每一個函數運行之後都會返回同一個jquery對象。
jQuery非常高效,因為你可以在主要工作是操作DOM元素的WEB編程環境下最大限度的發揮出函數型程式設計的威力。你也許是jQuery的粉絲,並且覺得這種函數式的操作非常贊。但是,這不是需要完全把這種編程模式到處使用的理由。我們通過下面這個jQuery源碼來看個例子:
12345678 |
jQuery.fn = jQuery.prototype = { init: function( selector, context ) { ... }, size: function() { return this.length; }, each: function( callback, args ) { return jQuery.each( this, callback, args ); }, ready: function( fn ) { ... } :} |
可以看到,jQuery對象有他自己的原型,也有一些自有的方法:比如size和each。更有意思的是,當你寫jQuery的外掛程式的時候,你只是給通過添加一個函數擴充了原型方法。至此,所有高效的js編程,都是兩種模式混合的結果。當然jQuery中函數型的模式更加明顯一些。
javascript中的對象
真正物件導向編程中的對象與javascript中的對象有一個很明顯的不同。在物件導向的語言中,類是你要使用的對象的藍本。在javascript中,你所使用的對象的藍本是一個資料字典或者一個函數。當你在js中建立了一個對象,你就擁有一個你可以填進去任何資料的空的資料字典。
之前說過,通過一些方法,你可以建立一些自訂的對象或者繼承自現有的對象。這隻在js中有效。
當你需要通過添加中間的轉化來接近真正的物件導向語言的時候,在js中有兩種辦法:閉包和原型。
在講述那兩種辦法之前,我們先來講講js中的物件類型和他的用法。
你可以通過new關鍵字來建立一個空的對象,然後你可以給這個對象添加進去你想要的內容:
123456789101112131415161718 |
var person = new Object();person.Name = "Dino";person.LastName = "Esposito";person.BirthDate = new Date(1979,10,17)person.getAge = function() { var today = new Date(); var thisDay = today.getDate(); var thisMonth = today.getMonth(); var thisYear = today.getFullYear(); var age = thisYear-this.BirthDate.getFullYear()-1; if (thisMonth > this.BirthDate.getMonth()) age = age +1; else if (thisMonth == this.BirthDate.getMonth() && thisDay >= this.BirthDate.getDate()) age = age +1; return age;} |
我們現在擁有一個名為person的對象,但是沒有Person對象。在js中,所有的原聲對象都有一個唯讀prototype屬性。通過這個屬性,你可以用來提供一些函數,這些函數在使用new關鍵字的時候,新產生的對象可以與原對象共用這些方法。下面有兩種方法可以在js中實現物件導向編程。
通過閉包實現物件導向
閉包是程式設計語言中的一個基本概念。在js中,閉包是指那些擁有同樣內容相關的變數和方法的一個函數。下面這個例子就是使用閉包來類比一個Person類:
1234567891011121314151617181920 |
var Person = function(name, lastname, birthdate){ this.Name = name; this.LastName = lastname; this.BirthDate = birthdate; this.getAge = function() { var today = new Date(); var thisDay = today.getDate(); var thisMonth = today.getMonth(); var thisYear = today.getFullYear(); var age = thisYear-this.BirthDate.getFullYear()-1; if (thisMonth > this.BirthDate.getMonth()) age = age +1; else if (thisMonth == this.BirthDate.getMonth() && thisDay >= this.BirthDate.getDate()) age = age +1; return age; }} |
可以看出,閉包就是父類的建構函式。在閉包模式中,構造器包含類成員的聲明,並且這些成員都是封裝起來的。另外,成員都是基於執行個體的,所以會消耗記憶體。如何使用呢:
12 |
var p = new Person('Rock','ux',new Date());alert(p.name + 'is' + p.getAge()); |
閉包模式是完全封裝的,僅此而已。
原型模式的物件導向方法
通過js中prototype對象,你可以定義一個類的結構。下面這個例子用來說明怎樣避免使用閉包來重寫Person類。
12345678910111213141516171819202122232425262728 |
// 構造器var Person = function(name, lastname, birthdate){ this.initialize(name, lastname, birthdate);} // 成員Person.prototype.initialize(name, lastname, birthdate){ this.Name = name; this.LastName = lastname; this.BirthDate = birthdate;}Person.prototype.getAge = function(){ var today = new Date(); var thisDay = today.getDate(); var thisMonth = today.getMonth(); var thisYear = today.getFullYear(); var age = thisYear-this.BirthDate.getFullYear()-1; if (thisMonth > this.BirthDate.getMonth()) age = age +1; else if (thisMonth == this.BirthDate.getMonth() && thisDay >= this.BirthDate.getDate()) age = age +1; return age;} |
在原型模式下面,構造器和成員之間結構清晰,但是建構函式是必須的。這裡並沒有私人成員,var關鍵字可以讓其只在閉包中有效。你可以通過定義setter/getter來操作你需要的屬性,但是這些屬性在類外面也是可以直接存取修改的。你可以通過一些特殊的操作(比如添加首碼)來定義私人變數。只是一種辦法而已。
原型模式可以很簡單的實現繼承:
12345 |
Developer = function Developer(name, lastname, birthdate){ this.initialize(name, lastname, birthdate);}Developer.prototype = new Person(); |
需要注意的是,你必須通過使用this關鍵字來訪問原型中的相關成員方法。
閉包還是原型?
在原型模式中,所有的成員都是共用的。所以,相比而言,記憶體的開銷要小一些。除去文法方面的不同,通過原型模式定義的類更加接近經典意義上的物件導向語言。
閉包還是原型的選擇還要從效能和瀏覽器的相容性來考慮。原型模式的載入速度不錯,而且在Firefox中效能也很好。(相比而言,閉包模式在IE下面的載入更快一些。)原型模式對於智能感知支援不錯,這樣在一些IDE中(比如VS)就能得到很好的工具支援。原型模式下,你不必通過建立一個執行個體來查看類型資訊,閉包模式下就不行。最後,調試的時候,原型模式可以方便的訪問私人成員,閉包模式下面得多幾步操作才行。
舉個例子,微軟的ajax庫使用的就是原型模式(閉包模式從未被考慮)。
結論
Javascript並不是純粹的函數型或者物件導向的語言。但是他從兩種語言都有借鑒,所以你需要做好一些準備。
如今,Javascript對於編寫用戶端的網頁和行動裝置 App程式來說必不可少。而且現在的指令碼編寫也不像當初這個語言開始使用的時候了。現在我們經常使用js來做用戶端的表單驗證等程式。為了讓開發進程更加的平穩,我們也需要一些js庫。jQuery就是最好的例證。
隨著jQuery大放光芒,函數型變成再次在js語言中相對於物件導向得到了更多的關注。最終,選擇什麼還是在你。你也不必像選擇一種信仰似的選擇一種開發模式,開發中,兩者混合使用是非常常見的事情。
如前所述,如果我是前端開發,我可能會使用jQuery,然後使用一些匿名函數,不去關心什麼自訂的對象。如果我是要自己寫一個js的庫,那麼我肯定會選擇物件導向開發,或者使用閉包模式或者使用原型模式。
點擊查看原文