本文章記錄本人在學習 函數式 中理解到的一些東西,加深記憶和而且整理記錄下來,方便以後的複習。編程
在近期看到了《JavaScript函數式編程》這本書預售的時候就定了下來。主要目的是我的目前仍是不理解什麼是函數式編程。在本身學習的過程當中一直聽到身邊的人說面向過程編程和麪向對象編程,而函數式就很是少。爲了本身不要落後於其餘同窗的腳步,故想以寫筆記的方式去分享和記錄本身閱讀中所汲取的知識。數組
書中用了一句簡單的話來回答了什麼是函數式編程:數據結構
函數式編程經過使用函數來將值轉換爲抽象單元,接着用於構建軟件系統。閉包
我以爲必定有同窗看了這一句仍是不怎麼動什麼是函數式編程,且爲何要使用函數式編程。後面的不少例子都使用到了Underscore
。函數式編程
抽象方法是指隱藏了細節的函數。舉一個書中的例子,一個檢測輸出年齡值的函數(主要是關於錯誤和警告的報告):函數
function parseAge(age) { if (!_.isString(age)) throw new Error("Expecting a string"); var a; console.log("Attempting to parse an age"); a = parseInt(age, 10); if (_.isNaN(a)) { console.log(["Could not parse age: "].join()); a = 0; } return a; }
上面的函數判斷咱們是否是輸入一個年齡,且必須是字符串形式。接着就是來運行這個函數:學習
parseAge("42"); //=> 42 parseAge(42); //=> Error:Expecting a string parseAge("hhhh"); //=> 0
上面的parseAge
函數工做正常沒有什麼問題。若是咱們要修改輸出錯誤=信息和警告的呈現方式、那麼就須要修改相應的代碼行,以及其餘地方的輸出模式。書中給的方法是經過將它們抽象成不一樣的函數來實現:設計
function fail(thing) { throw new Error(thing); } function warn(thing) { console.log(["WARNING:", thing].join('')); } function note(thing) { console.log(["NOTE:", thing].join('')); }
接着就是使用上面的函數,去重構parseAge
這個函數。code
funciton parseAge(age) { if (!_.isString(age)) fail("Expecting a string"); var a; note("Attempting to parse an age"); a = parseInt(age, 10); if (_.isNaN(a)) { warn(["Could not parse age:", age].join("")); a = 0; } return a; }
把報告錯誤的代碼都放到不一樣的函數裏去,且重構後的parseAge
和以前的也沒有多大的變化。可是不一樣的就是如今報告錯誤、信息和警告的想法已經被抽象化。錯誤、信息和警告的報告結果也是徹底被修改了。對象
這麼作是,因爲行爲包含在單一的函數中,因此函數能夠被可以提供相似行爲的新函數取代,或直接被徹底不一樣的行爲取代。
這個標題很容易理解,舉個例子。像咱們常用iife
來避免全局的污染,這就是一個封裝和隱蔽的很好例子。經過使用iife
來隱蔽本身的寫的一些變量和方法,目的就是不去污染全局的環境。這也是使用閉包的方式來隱蔽數據。
由於閉包也是一種函數。且和如今在學習函數式編程有莫大的關係。可是也不要忘記了以前學習的面向對象式封裝,畢竟這二者不能說誰更加的好。可是都掌握了也不是一件什麼壞事。一句老話:看需求。
隱藏數據和行爲(一般不方便於快速修改)只是一種講函數做爲抽象單元的方式。另外一種方式就是提供一種簡單地存儲方式和傳遞基本行爲的離線散單元。
書中一個小栗子,經過使用js
語法來索引數組中的一個值:
var arr = ['a', 'b', 'c']; arr[1] //=> b
雖然上面索引數組中的中的一個值很簡單,但並無辦法能夠在不把它放到函數裏的前提下,獲取這個行爲並根據須要來使用他/她。寫一個簡單函數nth
,用來索引數組中的一個值:
function nth(a, index) { return a[index]; }
接着運行:
nth(arr, 1); //=> b
運行成功,可是若是傳入一個空對象時,就會報錯了。所以,若是想圍繞nth
來實現函數抽象,咱們或許會設計下面的聲明:nth
返回一個存儲在容許索引訪問的數據類型中的有效袁術。這段聲明的關鍵在於索引數據的類型的概念。或許須要一個函數來判斷類型:
function isIndexed(data) { return _.isArray(data) || _.isString(data); }
接着繼續完善nth
函數。isIndexed
函數是一個提供了判斷某個數據是否爲字符串或者數組的抽象。
function nth(a, index) { if (!_.isNumber(index)) fail("Expected a number as the index"); if (!isIndexed(a)) fail("Not supported on non-indexed type"); if ((index < 0) || (index > a.length - 1)) fail("Index value is out of bounds"); return a[index]; }
從index
抽對象構建nth
函數抽象的方式同樣,也能夠以一樣的方式來構建一個second
抽象:
function second(a) { return nth(a, 1); }
函數second
容許在一個不一樣但相關的狀況下,正確的使用nth
函數:
second(arr); //=> b
經過上面的栗子,就知道。咱們能夠把每一步都抽象成一個函數,把每個參數都抽象出來。雖然這樣寫感受定義了許多函數。不過這樣更加容易理解每一項的功能和流程。
JavaScript 的對象原型模型是一個豐富且基礎的數據方案。
由於js
沒有類的緣由,就有了許多模擬類的方法,且在ES6
上也出現了class
關鍵字。儘管類有許多長處,但不少的時候js
應用程序的數據需求幣類中的簡單的要多。
基於類的對象系統的一個有理的論據是實現用戶界面的歷史使用。
js
中的對象和數組已經可以知足咱們對數據的操做了,且Underscore
也是重點也是如何處理數組和對象。
實施和使用的簡易性是使用js
的核心數據結構進行數據建模的目的。這並非說面向對象或者基於類的方法就徹底沒有用。處理集合爲中心的函數式方式更加適合處理與人有關的數據,而面向對象的方法最適合模擬人。
在開始函數式編程前,須要先定義兩個經常使用且有用的函數:
function existy(x) { return x != null } function truthy(x) { return (x !== false) && existy(x); }
existy
函數旨在定義事物以前的存在。js
中就有兩個值能夠表示不存在:null
和undefined
。truthy
函數用來判斷一個對象是否應該認爲是true
的同義詞。
咱們能夠在不少地方使用到這兩個函數,其實函數式理念來自於它們的使用。有些同窗可能已經熟悉了許多js
實現中的map forEach
等方法。且Underscroe
也提供了許多相似的方法,這也許就是選擇Underscroe
來輔助學習函數式編程的緣由。
簡單說下就是:
大概瞭解了函數式編程以後。你可能會想這函數式編程不是很慢嗎?好比前面獲取數組索引,有必要定義一個函數來專門獲取嗎?直接用arr[index]
絕對比那些函數來的快。
var arr = [1, 2, 3, 4, 5]; // 最快 for (var i = 0; i < arr.length; i++) { console.log(arr[i]); } // 較慢 _.each(arr, function (val, index) { console.log(index); });
可是咱們在寫代碼的時候可能不會考慮的那麼深,也許使用函數的確比原生要慢一些。可是大多數狀況下也不會去在意那麼點時間,且如今有強大的v8
引擎,大部分狀況下的他都能很高效的編譯和執行咱們的js
代碼。因此咱們沒有必要在尚未寫出正確的代碼前考慮運算速度。
若是是我來選擇的話,可能會更加關注與代碼的風格。那種寫法寫的舒服看的舒服就使用哪種,固然也是要保證基本的運算速度下,以不至於慢的離譜。看的舒服的代碼比跑的快的代碼可能更加有成就感。
看完了第一章也是能夠小結一下js
的函數式編程。下面引用書上的總結:
單是構建抽象仍是不夠的,若是可以把強大的數據抽象結合來實現函數式編程效果會更加好。