用函數式編程技術編寫優美的 JavaScript |
![]() |
級別: 初級
Shantanu Bhattacharya (shantanu@justawordaway.com), 首席顧問, Siemens Information Systems Limited
2006 年 7 月 20 日
函數式或聲明性編程是非常強大的編程方法,正逐漸在軟件行業流行起來。這篇文章將介紹一些相關的函數式編程概念,并提供有效使用這些概念的示例。作者將解釋如何使用 JavaScript(TM)?(JavaScript 能導入函數式編程的構造和特性)編寫優美的代碼。
函數式編程語言在學術領域已經存在相當長一段時間了,但是從歷史上看,它們沒有豐富的工具和庫可供使用。隨著 .NET 平臺上的 Haskell 的出現,函數式編程變得更加流行。一些傳統的編程語言,例如 C++ 和 JavaScript,引入了由函數式編程提供的一些構造和特性。在許多情況下,JavaScript 的重復代碼導致了一些拙劣的編碼。如果使用函數式編程,就可以避免這些問題。此外,可以利用函數式編程風格編寫更加優美的回調。
![]() |
|
因為函數式編程采用了完全不同的組織程序的方式,所以那些習慣于采用命令式范例的程序員可能會發現函數式編程有點難學。在這篇文章中,您將了解一些關于如何采用函數式風格,用 JavaScript 編寫良好的、優美的代碼的示例。我將討論:
![]() ![]() |
![]() |
![]() |
在那些通過描述 “如何做” 指定解決問題的方法的語言中,許多開發人員都知道如何進行編碼。例如,要編寫一個計算階乘的函數,我可以編寫一個循環來描述程序,或者使用遞歸來查找所有數字的乘積。在這兩種情況下,計算的過程都在程序中進行了詳細說明。清單 1 顯示了一個計算階乘的可能使用的 C 代碼。
清單 1. 過程風格的階乘
|
這類語言也叫做過程性 編程語言,因為它們定義了解決問題的過程。函數式編程與這個原理有顯著不同。在函數式編程中,需要描述問題 “是什么”。 函數式編程語言又叫做聲明性 語言。同樣的計算階乘的程序可以寫成所有到 n 的數字的乘積。計算階乘的典型函數式程序看起來如 清單 2 中的示例所示。
清單 2. 函數式風格的階乘
|
第二個語句指明要得到從 1 開始的前 n 個數字的列表(take n [1..]
),然后找出它們的乘積,1 為基元。這個定義與前面的示例不同,沒有循環或遞歸。它就像階乘函數的算術定義。一旦了解了庫函數(take
和 foldr
)和標記(list notation [ ]
)的意義,編寫代碼就很容易,而且可讀性也很好。
![]() |
|
從歷史上看,函數式編程語言不太流行有各種原因。但是最近,有些函數式編程語言正在進入計算機行業。其中一個例子就是 .NET 平臺上的 Haskell。其他情況下,現有的一些語言借用了函數式編程語言中的一些概念。一些 C++ 實現中的迭代器和 continuation,以及 JavaScript 中提供的一些函數式構造(functional construct),就是這種借用的示例。但是,通過借用函數式構造,總的語言編程范例并沒有發生變化。JavaScript 并沒因為函數式構造的添加就變成了函數式編程語言。
我現在要討論 JavaScript 中的函數式構造的各種美妙之處,以及在日常編碼和工作中使用它們的方式。我們將從一些基本功能開始,然后用它們查看一些更有趣的應用。
在 JavaScript 中,可以編寫匿名函數或沒有名稱的函數。為什么需要這樣的函數?請繼續往下讀,但首先我們將學習如何編寫這樣一個函數。如果擁有以下 JavaScript 函數:
清單 3. 典型的函數
|
然后對應的匿名函數看起來應當如下所示:
清單 4. 匿名函數
|
要使用它,則需要編寫以下代碼:
清單 5. 應用匿名函數
|
也可以將函數作為值使用。還可以擁有一些所賦值是函數的變量。在最后一個示例中,還可以執行以下操作:
清單 6. 使用函數賦值
|
在上面 清單 6 的示例中,為變量 sum 賦的值是函數定義本身。這樣,sum 就成了一個函數,可以在任何地方調用。
JavaScript 允許用兩種方式調用函數,如清單 7 和 8 所示。
清單 7. 典型的函數應用
|
或
清單 8. 用函數作為表達式
|
所以也可以編寫以下代碼:
清單 9. 定義函數之后就可以立即使用它
|
可以在括號中編寫函數表達式,然后傳遞給參數,對參數進行運算。雖然在 清單 8 的示例中,有直接包含在括號中的函數名稱,但是按 清單 9 中所示方式使用它時,就不是這樣了。
也可以將函數作為參數傳遞給其他函數。雖然這不是什么新概念,但是在后續的示例中大量的使用了這個概念??梢詡鬟f函數參數,如 清單 10 所示。
清單 10. 將函數作為參數傳遞,并應用該函數
|
執行最后一個 alert 語句輸出了一個大小為 12 的值。
前一節介紹了一些使用函數式風格的編程概念。所給的示例并沒有包含所有的概念,它們在重要性方面也沒有先后順序,只是一些與這個討論有關的概念而已。下面對 JavaScript 中的函數式風格作一快速總結:
- 函數并不總是需要名稱。
- 函數可以像其他值一樣分配給變量。
- 函數表達式可以編寫并放在括號中,留待以后應用。
- 函數可以作為參數傳遞給其他函數。
這一節將介紹一些有效使用這些概念編寫優美的 JavaScript 代碼的示例。(使用 JavaScript 函數式風格,可以做許多超出這個討論范圍的事。)
- 擴展數組排序
- 先來編寫一個排序方法,可以根據數組元素的日期對數據進行排序。用 JavaScript 編寫這個方法非常簡單。數據對象的排序方法接受一個可選參數,這個可選參數就是比較函數。在這里,需要使用 清單 11 中的比較函數。
function (x,y) { return x.date – y.date; }
要得到需要的函數,請使用 清單 12 的示例。
清單 12. 排序函數的擴展arr.sort( function (x,y) { return x.date – y.date; } );
其中 arr 是類型數組對象。排序函數會根據 arr 數組中對象的日期對所有對象進行排序。比較函數和它的定義一起被傳遞給排序函數,以完成排序操作。使用這個函數:
- 每個 JavaScript 對象都有一個 date 屬性。
- JavaScript 的數組類型的排序函數接受可選參數,可選參數是用來排序的比較函數。這與 C 庫中的
qsort
函數類似。
- 動態生成 HTML 的優美代碼
- 在這個示例中,將看到如何編寫優美的代碼,從數組動態地生成 HTML。可以根據從數據中得到的值生成表格?;蛘?,也可以用數組的內容生成排序和未排序的列表。也可以生成垂直或水平的菜單項目。
清單 13 中的代碼風格通常被用來從數組生成動態 HTML。
清單 13. 生成動態 HTML 的普通代碼var str=' '; for (var i=0;i<arr.length;i++) { var element=arr[i]; str+=... HTML generation code... } document.write(str);
可以用 清單 14 的代碼替換這個代碼。
清單 14. 生成動態 HTML 的通用方式Array.prototype.fold=function(templateFn) { var len=this.length; var str=' '; for (var i=0 ; i<len ; i++) str+=templateFn(this[i]); return str; } function templateInstance(element) { return ... HTML generation code ... } document.write(arr.fold(templateInstance));
我使用
Array
類型的 prototype 屬性定義新函數 fold?,F在可以在后面定義的任何數組中使用該函數。 - 系列函數的應用
- 考慮以下這種情況:想用一組函數作為回調函數。為實現這一目的,將使用
window.setTimeout
函數,該函數有兩個參數。第一個參數是在第二個參數表示的毫秒數之后被調用的函數。清單 15 顯示了完成此操作的一種方法。
清單 15. 在回調中調用一組函數window.setTimeout(function(){alert(‘First!’);alert(‘Second!’);}, 5000);
清單 16 顯示了完成此操作的更好的方式。
清單 16. 調用系列函數的更好的方式Function.prototype.sequence=function(g) { var f=this; return function() { f();g(); } }; function alertFrst() { alert(‘First!’); } function alertSec() { alert(‘Second!’); } setTimeout( alertFrst.sequence(alertSec), 5000);
在處理事件時,如果想在調用完一個回調之后再調用一個回調,也可以使用 清單 16 中的代碼擴展。這可能是一個需要您自行完成的一個練習,現在您的興趣被點燃了吧。
![]() ![]() |
![]() |
在許多領域中都可以應用 JavaScript 中的函數式編程,以優美的方式完成日?;顒?。這篇文章中的示例只介紹了幾種情況。如果您找到了函數式編程的合適場景,并應用這些概念,那么您就會有更多的理解,并且可以增加您的優美程度。
![]() ![]() |
![]() |
學習
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文 。
- “Functional programming in the Java language: Use closures and higher order functions to write modular Java code”(developerworks,2004 年 7 月):學習如何使用函數式編程構造(例如封裝和更高級別的函數),用 Java 語言編寫結構良好的模塊化的代碼。
- “Beginning Haskell”(developerWorks,2001 年 9 月):請參閱這份教程,它提供了函數式編程范例的介紹,用 Haskell 98 語言進行了一些特別演示。
- Tutorial Papers in Functional Programming:找到與函數式編程有關的一些主題的多個資源。
- developerWorks Web 架構專區:利用針對 Web 技術的文章和教程來擴展您的站點開發技能。
- developerWorks technical events and webcasts:熱切關注最熱鬧的技術會議,它們可以縮短您的學習曲線、改進最令您頭疼的軟件項目的質量和結果。
獲得產品和技術
- 免費下載和學習資源:使用來自 developerWorks Web 架構專區的文章、教程和下載改進您的工作。
討論
- 參與 developerWorks 討論組。
- developerWorks blogs:加入 developerWorks 社區。
![]() ![]() |
![]() |
![]() | ||
![]() | Shantanu Bhattacharya 曾為各類應用程序軟件大量設計和創建架構,這些軟件中包括零售、系統集成、衛生保健軟件;基于 SNMP 和 TCP/IP 堆棧的網絡軟件;安全軟件;印度第一臺超級計算機的文件系統;印度導彈項目的實時軟件。目前他是一名首席架構師,在西門子位于 Bangalore 的分部從事衛生保健方面的工作。 |