函數式編程是一種被部分JavaScript程序員推崇的編程風格,更別說 Haskell 和 Scala 這種以函數式爲教義的語言。緣由是由於其能用較短的代碼實現功能,若是掌握得當,能達到代碼文檔化(代碼自己具備很高可讀性甚至能夠代替文檔)的效果,固然,氾濫使用也會使代碼可讀性變差。程序員
而柯里化(Currying)是一個屬於函數式編程的一個常見的技巧。簡單來講,函數柯里化就是對高階函數的降階處理。
咱們看下面這個函數:編程
function loc (a,b,c){ console.log(a+'-'+b+'-'+c); } loc('浙江','杭州','西湖區');//>>>浙江-杭州-西湖區
這是一個接收三個地名字符串的函數,功能是將三個地名字符串拼接到一塊兒成爲一個詳細的地址。
咱們試試只傳入兩個參數:segmentfault
loc('浙江','杭州');//>>>浙江-杭州-undefined
毫無疑義地,這個函數仍是會正常執行,只是本來屬於「區」的位置因爲沒有接收到參數而成了undefined
。數組
其實這種狀況不少,好比咱們還要生成浙江-杭州-餘杭區
,浙江-杭州-拱墅區
這樣的地名,可是咱們不必每次都從新把浙江-杭州
從新拼接一遍,因此你是否是會想,能不能只經過一個函數,把已經拼接過的字符串緩存起來,只去拼接新的字符串呢?咱們或許能夠把以前的函數改一下:緩存
function loc (a) { return function(b){ return function(c){ console.log(a+'-'+b+'-'+c); } } }
好奇怪!這個loc函數只接收一個參數,而返回一個新的函數,這個函數也只接受一個參數,裏面一樣返回一個函數,最後一個函數才返回三個參數拼接的字符串。
看起來是一個嵌套關係,好吧,讓咱們看看它是否能實現剛剛的想法:閉包
var Zhejiang = loc('浙江'); var Hangzhou = Zhejiang('杭州'); var Xihu = Hangzhou('西湖區'); //浙江-杭州-西湖區 var Yuhang = Hangzhou('餘杭區'); //浙江-杭州-餘杭區 var Lucheng = Zhejiang('溫州')('鹿城區'); //浙江-溫州-鹿城區
看,經過這樣的形式,咱們輕鬆實現定製化函數啦!loc('杭州')
不會急着把地名都拼接好,而是把杭州
先存到閉包中,在須要拼接的時候纔用到它。app
讓你意外的是,這就是柯里化的基本思想,簡單地讓人猝不及防。函數式編程
不,這不是你想要的結果,至少你已經考慮到一種恐怖的狀況:若是參數有許多——好比100個,是否是要寫一個嵌套一百次的函數?函數
好在,咱們能夠寫一個通用函數來優雅地建立柯里化函數:spa
function curry(fn) { var outerArgs = Array.prototype.slice.call(arguments, 1); return function() { var innerArgs = Array.prototype.slice.call(arguments), finalArgs = outerArgs.concat(innerArgs); return fn.apply(null, finalArgs); }; }
有了這個基本函數以後,咱們能夠柯里化其餘普通函數:
//一個普通函數 function loc(a,b,c){ console.log(a+'-'+b+'-'+c); } var workIn = curry(loc,'浙江','杭州','餘杭區'); workIn();// >>> 浙江-杭州-餘杭區
固然也能夠這樣定製:
var zj = curry(loc,'浙江'); var city = curry(zj,'杭州'); city('餘杭區'); //>>> 浙江-杭州-餘杭區 city('上城區'); //>>> 浙江-杭州-上城區 zj('溫州','鹿城區');//>>> 浙江-溫州-鹿城區
簡直優雅。
如下咱們來簡單分析如下這個通用函數:
function curry(fn) { var outerArgs = Array.prototype.slice.call(arguments, 1); return function() { var innerArgs = Array.prototype.slice.call(arguments), finalArgs = outerArgs.concat(innerArgs); return fn.apply(null, finalArgs); }; }
咱們看這個curry
函數,顯示接受一個參數,那就是須要被柯里化的函數。同時,由於JS神奇的函數傳參,咱們能夠在curry
繼續放更多的參數,這些參數在函數體內部將以參數列表的形式存在,你能夠經過arguments
引用這個參數列表。注意,arguments
引用的是一個參數列表而不是數組(雖然很像),因此數組的不少方法它都沒有。固然,這個難不倒咱們,還記得我上一篇文章裏提到的apply()
黑科技嗎?傳送門:《快速理解JavaScript中apply()和call()的用法和用途》
outerArgs
就是獲取除了第一個參數以外的參數列表。
而在返回的函數裏,innerArgs
獲取的是調用這個返回函數時傳入的全部參數,好比以前city('上城區')
裏面的'上城區'。
而finalArgs
則是拼接這兩個數組(注意此時兩個參數列表都已是正宗的數組,因此纔可使用concat
方法)。
最後,使用apply
,把全部參數組成的列表傳入到原來的函數,其實質就是在調用原始的函數,由於此時的參數都已齊全。
結尾福利: