幾十年來,函數式編程一直是計算機科學狂熱者的至愛,因爲數學的純潔性和謎通常的本質, 它被埋藏在計算機實驗室,只有數據學家和有但願得到博士學位的人士使用。可是如今,它正經歷一場復興, 這要感謝一些現代語言好比Python,Julia,Ruby,Clojure以及——但不是最後一個——Javascript。 javascript
你是說Javascript?這個WEB腳本語言?沒錯!html
Javascript已經被證實是一項長期以來都沒有消失的重要的技術。這主要是因爲它擴展的一些框架和庫而使其具備重生的能力, 好比backbone.js,jQuery,Dojo,underscore.js等等。這與Javascript函數式編程語言的真實身份直接相關。 對Javascript函數式編程的理解很重要,而且在至關長的一段時間會對各類水平的程序員頗有用。 java
爲何呢?函數式編程很是強大、健壯而且優雅。它對於大型數據結構很是有用而且高效。 Javascript做爲一個客戶端腳本語言,在應對日益複雜的網站時,函數式地操做DOM、 組織API響應以及完成一些其它任務會很是有好處。 node
在這本書裏,你將會學習用Javascript進行函數式編程所須要知道的一切:如何用函數式編程構建你的Javascript web應用, 如何解鎖Javascript隱藏的力量,如何編寫更強大的代碼,而且因爲程序更小,使得代碼更容易維護,可以更快被下載, 而且花費更少的開支。你還會學到函數式編程的核心概念,以及如何將它們應用到Javascript, 還有將Javascript做爲函數式語言時如何迴避一些問題,如何在Javascript中混合使用函數式編程和麪向對象編程。 程序員
不過在咱們開始前,先來作個實驗。 web
也許快速舉個例子是介紹Javascript函數式編程最好的方式。咱們將用Javascript完成一些任務—— 一個使用傳統、原生的方法,另外一個使用函數式編程。而後咱們將會比較這兩種方法。 數據庫
爲了追求真實感,咱們來作一個電子商務網站,一個郵購咖啡豆的公司。這個網站會銷售好幾種類型的咖啡, 有不一樣的品質,固然也有不一樣的價格。 編程
首先,咱們開始寫程序。爲了讓這個例子接地氣,咱們須要建立一些對象來保存數據。若是須要的話咱們能夠從數據庫裏取值。 可是如今咱們假設他們是靜態定義的: 數組
// create some objects to store the data. var columbian = {  name: 'columbian', basePrice: 5 }; var frenchRoast = { name: 'french roast', basePrice: 8 }; var decaf = { name: 'decaf', basePrice: 6 }; // 咱們將使用輔助函數計算價格 // 根據size打印到一個HTML的列表中 function printPrice(coffee, size) { if (size == 'small') { var price = coffee.basePrice + 2; } else if (size == 'medium') { var price = coffee.basePrice + 4; } else { var price = coffee.basePrice + 6; } // create the new html list item var node = document.createElement("li"); var label = coffee.name + ' ' + size; var textnode = document.createTextNode(label+' price: $'+price); node.appendChild(textnode); document.getElementById('products').appendChild(node); } // 如今咱們只需根據咖啡的各類價格和size的組合調用printPrice函數 printPrice(columbian, 'small'); printPrice(columbian, 'medium'); printPrice(columbian, 'large'); printPrice(frenchRoast, 'small'); printPrice(frenchRoast, 'medium'); printPrice(frenchRoast, 'large'); printPrice(decaf, 'small'); printPrice(decaf, 'medium'); printPrice(decaf, 'large');
如你所見,這個代碼很是基礎。若是如今有更多的咖啡種類而不僅是這三個改怎麼辦?若是有20個,甚至50個? 若是有更多的size呢?若是有有機和無機之分呢?這將會很快將代碼量變得巨大無比! 瀏覽器
採用這種方法,咱們讓機器去打印每一種咖啡類型和每個size。這就是採用這種命令式方法的基本問題。
命令式的代碼一步一步地告訴電腦須要作什麼來解決問題,相反,函數式編程追求用數學方式來描述問題, 其他的交給電腦來作。
經過更函數式一些的方式,一樣的應用能夠這樣來寫:
// 從接口中分解數據和邏輯 var printPrice = function(price, label) { var node = document.createElement("li"); var textnode = document.createTextNode(label+' price: $'+price); node.appendChild(textnode); document.getElementById('products 2').appendChild(node); } // 爲每種咖啡建立函數對象 var columbian = function(){ this.name = 'columbian'; this.basePrice = 5; }; var frenchRoast = function(){ this.name = 'french roast'; this.basePrice = 8; }; var decaf = function(){ this.name = 'decaf'; this.basePrice = 6; }; // 爲每種size經過字面量建立對象 var small = { getPrice: function(){return this.basePrice + 2}, getLabel: function(){return this.name + ' small'} }; var medium = { getPrice: function(){return this.basePrice + 4}, getLabel: function(){return this.name + ' medium'} }; var large = { getPrice: function(){return this.basePrice + 6}, getLabel: function(){return this.name + ' large'} }; // 將全部咖啡的種類和size放到數組裏 var coffeeTypes = [columbian, frenchRoast, decaf]; var coffeeSizes = [small, medium, large]; // 建立由上面內容組成的新對象,並把它們放到一個新數組裏 var coffees = coffeeTypes.reduce(function(previous, current) { var newCoffee = coffeeSizes.map(function(mixin) { // `plusmix`是函數式的minxin, 見第7章 var newCoffeeObj = plusMixin(current, mixin); return new newCoffeeObj(); }); return previous.concat(newCoffee); },[]); // 如今咱們已經定義瞭如何得到全部咖啡種類和size組合方式的價格,如今能夠直接打印它們了 coffees.forEach(function(coffee){ printPrice(coffee.getPrice(),coffee.getLabel()); });
首先須要明確的是這個代碼更加模塊化了。如今新增一種size或者信新增一個咖啡種類就像下面的代碼這樣簡單:
var peruvian = function(){ this.name = 'peruvian'; this.basePrice = 11; }; var extraLarge = { getPrice: function(){return this.basePrice + 10}, getLabel: function(){return this.name + ' extra large'} }; coffeeTypes.push(Peruvian); coffeeSizes.push(extraLarge);
咖啡對象的數組和size對象的數組混合(mix)到了一塊兒,也就是他們的方法和成員變量被組合到了一起 ——經過一個叫「plusMinxin」的自定義函數(詳見第七章)。這些咖啡類型的類(columbian, frenchRoast, decaf)包含了成員變量, 而這些size對象(small, medium, large)包含了獲取名稱和計算價格的方法。 」混合」(minxing)這個動做經過一個map操做來起做用,也就是對數組中的每個成員執行一個純函數並返回一個新的函數, 而後這些返回的函數被放到了一個reduce函數中被操做,reduce也是一個高階函數,和map有些像, 只是reduce把數組裏的全部元素處理後組合到了一個東西里面。最終,新的數組包含了全部可能的種類和size的組合, 這個數組經過forEach方法遍歷,forEach也是一個高階函數,它會讓數組裏面每個對象做爲參數執行一遍回調函數。 在這個例子裏,這個回調函數是一個匿名函數,它獲取這些對象後,以對象的getPrice()和getLabel() 兩個方法的返回值做爲參數調用printPrice函數。
實際上,咱們可讓這個例子更加函數式:去掉coffees變量,並將函數串到一塊兒鏈式調用,這也是函數式編程的一個小技巧。
coffeeTypes.reduce(function(previous, current) { var newCoffee = coffeeSizes.map(function(mixin) { // `plusMixin` function for functional mixins, see Ch.7 var newCoffeeObj = plusMixin(current, mixin); return new newCoffeeObj(); }); return previous.concat(newCoffee); },[]).forEach(function(coffee) { printPrice(coffee.getPrice(),coffee.getLabel()); });
這樣,控制流沒有像命令式代碼那樣從頭至尾的順序進行。在函數式編程裏,map函數和其它高階函數代替了for和while循環, 只有少許關鍵的代碼是在順序執行。 這使得新接觸的人在閱讀這樣範式的代碼有些困難,可是一旦你可以欣賞它,你就會發現這根本沒啥難的, 並且這樣寫看起來更好。
這個例子僅僅是剛開始展露Javascript中函數式編程能作什麼。經過這本書,你將會看到更多函數式實現的強悍的例子。
首先,採用函數式風格的優勢已經明確了。 其次,不要懼怕函數式編程。的確,它每每被認爲是編程語言的純邏輯形式,可是咱們不須要理解lambda演算也可以在平常任務中應用它。 實際上,經過把咱們的程序拆分紅小的片斷,它們變得更容易被理解、維護,也更加可靠。 map和reduce函數是Javascript中不太被知道的內建函數,然而咱們將要關注它們。
Javascript是一個腳本語言,可交互,易使用,不須要編譯。咱們甚至不須要下載任何開發軟件, 你最喜歡的瀏覽器就能夠做爲開發環境的解釋器。
感興趣嗎?好,咱們開始!