聽說全部的函數式程序員都會寫本身的函數庫,函數式Javascript程序員也不例外。 隨着現在開源代碼分享平臺如GitHab、Bower和NPM的涌現,對這些函數庫進行分享、變得及補充變得愈來愈容易。 如今已經有不少Javascript的函數式變成苦,從小巧的工具集到龐大的模塊庫都有。 javascript
每個庫都宣揚着本身的函數式編程風格。從一本正經的數學風格到靈活鬆散的非正式風格,每個庫都不盡相同, 然而他們他們有一個共同的特色:都是經過抽象的Javascript函數式能力來增進代碼的重用行、可讀性和健壯性。 css
然而直到寫這本書的時候,尚未一個函數庫成爲事實上的標準。有人可能會說underscore.js是, 不過在後面的章節你會看到,可能避免使用underscore.js是明智的。 html
Underscore在不少人眼裏已經成爲函數式Javascript庫的標準。它成熟穩定, 其建立者Jeremy Ashkenas也是Backbone.js和Coffeescript的建立者。 Underscore其實是對Ruby的Enumerable模塊的從新實現, 這也解釋了爲何Coffeescript也是受Ruby影響。 java
與jQuery類似,Underscore並不改變Javascript原生對象,而是用一個符號來定義本身的對象, 就是下劃線(underscore)字符「_」。因此使用Underscore會是這個樣子: node
var x = _.map([1,2,3], Math.sqrt); // Underscore的map函數 console.log(x.toString());
咱們已經見過Javascript數組原生的map()方法,它是這樣用的:程序員
var x = [1,2,3].map(Math.sqrt);
不一樣的是,用underscore時,數組對象和回調函數都是做爲參數傳入給underscore的map()方法(_.map)的, 而不是像數組原生的map()方法(Array.prototype.map)那樣只需傳遞迴調。 編程
不過underscore除了map()還有不少內建函數,他們都是很是好用的函數, 好比find()、invoke()、pluck()、sortBy()、groupBy()等等。 設計模式
var greetings = [{ origin: 'spanish', value: 'hola' }, { origin: 'english', value: 'hello' }]; console.log(_.pluck(greetings, 'value')); // 獲取一個對象的屬性. // 返回: ['hola', 'hello'] console.log(_.find(greetings, function(s) { return s.origin == 'spanish'; })); // 查找第一個回調函數返回真的元素 // 返回: {origin: 'spanish', value: 'hola'} greetings = greetings.concat(_.object(['origin', 'value'], ['french', 'bonjour'])); console.log(greetings); // _.object經過合併兩個數組來創建一個對象 // 返回: [{origin: 'spanish', value: 'hola'}, //{origin: 'english', value: 'hello'}, //{origin: 'french', value: 'bonjour'}]
而且它還提供了鏈式調用方法數組
var g = _.chain(greetings) .sortBy(function(x) { return x.value.length }) .pluck('origin') .map(function(x) { return x.charAt(0).toUpperCase() + x.slice(1) }) .reduce(function(x, y) { return x + ' ' + y }, '') .value(); // 應用這些函數 // 返回: 'Spanish English French' console.log(g);
儘管underscore易於使用而且被社區改進,他仍是在遭受批評。underscore強迫你編寫過於冗長的代碼, 並鼓勵你使用錯誤的模式。underscore的結構並不完美,甚至不夠函數式! 數據結構
就在Brian Lonsdorf在YouTube上發表名爲「嘿,underscore,你作錯了」的講話不久以後, underscore在發行的1.7.0版本中明確地阻止了咱們擴展函數,好比map()、reduce()和filter()等等。
_.prototype.map = function(obj, iterate, [context]) { if (Array.prototype.map && obj.map === Array.prototype.map) return obj.map(iterate, context); // ... };
在範疇論的形式裏,map是一個同態函子接口(詳見第五章《範疇輪》)。咱們應該可以把map定義爲函子, 不管咱們是否須要這樣。因此說underscore不是很函數式。
而且因爲Javascript不具備內建的不可變數據,函數式庫應該十分當心地避免輔助函數改變傳入的對象。 下面展現了一個針對這個問題的例子。代碼中你會了一個新的選線列表,其中有一個選擇項被設爲了默認項。 實際上原來的列表被修改了。
function getSelectedOptions(id, value) { options = document.querySelectorAll('#' + id + ' option'); var newOptions = _.map(options, function(opt) { if (opt.text == value) { opt.selected = true; opt.text += ' (this is the default)'; } else { opt.selected = false; } return opt; }); return newOptions; } var optionsHelp = getSelectedOptions('timezones', 'Chicago');
咱們應當插入一行「opt = opt.cloneNode()」,讓回調函數對傳入的列表中每個元素創建一份拷貝。 underscore的map()函數爲了獲得性能而破壞了函數式的風水。原生的Array.prototype.map()不要求這些, 由於它會創建一個拷貝,然而它沒法做用於nodelist集合。
Underscore也許並無要追求函數式編程數學上的正確性,不過它也歷來沒有想要把Javascript擴展或者轉變爲一個純函數語言。 它把本身定義爲一個提供一大堆有用的函數式編程輔助函數的Javascript庫。 也許它比那些僞造得看起來像函數式輔助函數的玩意兒要好些,不過它也不是一個嚴肅的函數式庫。
那麼有沒有更好的庫呢?一個創建在數學之上的庫?
有時,真實世界比小說更離奇。
Fantasy Land是一個函數式基礎庫的集合,也是一份關於如何在Javascript中實現「代數結構」的規格。 更確切地說,Fantasy Land闡述了通常代數結構(簡稱代數)的互操做性:monads、monoids、setoids、 函子(functors)、鏈(chains)等等。這些名字可能聽起來很嚇人,不過他們只是一系列值、 一系列操做以及一些必需要遵照的規定。換句話說,他們只不過是對象。
下圖展現了他們是如何工做的。每個代數是一個單獨的Fantasy Land規格, 它可能依賴於另外一個須要實現的代數。
這裏列出一些代數的規格:
這個列表還有不少內容
咱們不須要知道麼一個代數的確切含義是什麼,可是它的確頗有幫助,尤爲是當你編寫符合這些這些規則的本身的庫的時候。 這不僅是抽象的玩意兒,它對一個叫作範疇論的高度抽象的東西的含義進行了歸納。第五章將對範疇論進行全面的解釋。
Fantasy Land不僅告訴了咱們如何實現函數式編程,它還提供了一個Javascript的函數式模塊集。 然而裏面有不少不完整的東西,而且文檔也很不完善。不過Fantasy Land不是對這個開源規格的惟一實現。 還有一個實現的庫叫作Bilby.js。
Bilby是個啥?它可不是夢幻大陸(Fantasy Land)上的神話生物,而是地球上的介於老鼠和兔子之間的一種怪異而可愛的動物, 中文名是兔耳袋狸。儘管如此,bilby.js庫聽從Fantasy Land的規格。
實際上,bilby是一個嚴肅的函數庫。如它的文檔中所描述:嚴肅,意味着它應用範疇論來實現高度抽象代碼; 函數式,意味着它可使程序引用透明。Wow,它還真夠嚴肅的。文檔的地址是 http://bilby.brianmckenna.org/。 它提供瞭如下內容:
目前,Bilby.js這個已經很成熟的的庫符合了Fantasy Land關於代數結構的規格。 要寫徹底函數式語言的代碼,Bilby.js是一個優秀的資源。
咱們來看個例子
// bilby的環境是多元方法的不可變結構 var shapes1 = bilby.environment() // 定義方法 .method( 'area', // 方法的名稱 function(a){return typeof(a) == 'rect'}, // 斷言 function(a){return a.x * a.y} // 實現 ) // 定義屬性,相似於定義一個方法,裏面只有總返回true的斷言 .property( 'name', // 名稱 'shape'); // 函數 // 如今咱們能夠把它重載 var shapes2 = shapes1 .method( 'area', function(a){return typeof(a) == 'circle'}, function(a){return a.r * a.r * Math.PI} ); var shapes3 = shapes2 .method( 'area', function(a){return typeof(a) == 'triangle'}, function(a){return a.height * a.base / 2} ); // 如今咱們能夠像這樣作點什麼 var objs = [{type:'circle', r:5}, {type:'rect', x:2, y:3}]; var areas = objs.map(shapes3.area); // 或者這樣 var totalArea = objs.map(shapes3.area).reduce(add);
這就是範疇論和特定多態的實踐。再囉嗦一次:範疇論將會在第五章全面講解。
事實上,Bilby和Fantasy Land真的讓Javascript之上的函數式編程成爲了可能。 儘管能夠看到計算機科學發生着突飛猛進的變化,可是這個世界仍未準備好迎接Bilby和Fantasy Land 所推進的頑固的函數式編程風格。
也許在函數式Javascript的恐慌地帶的如此壯麗的一個庫並非咱們想要的。 畢竟咱們的出發點是尋找用於補充Javascript的函數式技術,而不是創建函數式編程信條。 如今讓咱們把注意力轉向另外一個新庫:Lazy.js。
Lazy是一個實用的庫,它更大程度上是沿着Underscore的路線,不過它有惰性求值策略。正因如此, Lazy讓即刻解釋的語言本不可能完成的函數式計算變成了可能。它還會顯著提高性能。
Lazy庫還很年輕,可是在它背後有旺盛的社區熱度和強勁的動力。
Lazy的主意是,咱們可以迭代的全部東西都是一個序列。因爲這個庫用方法執行的前後來控制順序, 不少很酷的事情就能夠實現了:異步循環(並行編程)、無限序列、函數式響應式編程等等。
下面的例子展現了一下各類情形的代碼:
// 得到一首歌歌詞的前三行 var lyrics = "我徘徊在海之濱山之巔\n越此城鎮越彼鄉園\n ... // 若是沒有惰性,整個歌詞會先根據換行來分割 console.log(lyrics.split('\n').slice(0, 3)); // 有了惰性,能夠只文本分割出來前三行 // 歌詞甚至能夠無限的長! console.log(Lazy(lyrics).split('\n').take(3));
// 前十個能被3整除的平方數 var oneTo1000 = Lazy.range(1, 1000).toArray(); var sequence = Lazy(oneTo1000) .map(function(x) { return x * x; }) .filter(function(x) { return x % 3 === 0; }) .take(10) .each(function(x) { console.log(x); });
// 對無限序列的異步循環 var asyncSequence = Lazy.generate(function(x) { return x++ }) .async(100) // 每兩個元素間隔0.100秒 .take(20) // 只計算前20項 .each(function(e) { // 開始對序列進行循環 console.log(new Date().getMilliseconds() + ": " + e); });
更多例子參見第四章。
不過Lazy庫的這個主意並不能保證它徹底的正確性。它還有一個前輩,Bacon.js,他們的工做方式差很少。
這是Bacon.js的logo:
這個大鬍子牛仔是一個函數式響應式編程的庫。函數式響應式編程的意思是: 函數式設計模式用於展現響應式的常常變化的值,好比鼠標在屏幕上的位置,或者公司股票的價格。 跟Lazy經過須要時才計算值而避免建立無限循環的序列的方法相同, Bacon能夠避免實時計算隨時在發生變化的值,直到須要這個值的最後一秒。
在Lazy中被稱爲序列的東西在Bacon中是事件流和屬性,由於這樣更適合於使用事件(onmouseover, onkeydown等等)以及響應屬性(滾動位置、鼠標位置、toggle等等)。
Bacon.fromEventTarget(document.body, "click") .onValue(function() { alert("Bacon!") });
Bacon比Lazy稍老一些,可是它的功能集差很少是Lazy的一半,社區熱度差很少。
Javascript函數式編程的庫實在太多了,沒法在本書中一一展現。咱們再來簡單看幾個吧。
$('#mydiv').fadeIn().css('left': 50).alert('hi!');關於這個的詳細解釋能夠在第七章《Javascript的函數式和麪向對象編程》中找到
$('li').css('left': function(index){return index*50});