JavaScript也玩私人訂製——玩轉函數柯里化

函數式編程是一種被部分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,把全部參數組成的列表傳入到原來的函數,其實質就是在調用原始的函數,由於此時的參數都已齊全。

結尾福利:

clipboard.png

相關文章
相關標籤/搜索