JavaScript柯里化

Currying柯里化是函數式語言都有的一個特性,如Perl,Python,JavaScript。本篇就借用一下JavaScript,介紹一下柯里化的思想及應用。javascript

假設函數庫裏提供這樣一個拼接URL地址的函數:html

function simpleURL(protocol, domain, path) {
    return protocol + "://" + domain + "/" + path;
}
simpleURL('http','www.jackzxl.net', 'index.html');      //http://www.jackzxl.net/index.html
複製代碼

這是個最普通的函數毫無新意。但對於你的站點來講,第一個參數固定爲http,第二個參數固定爲www.jackzxl.net,惟一須要改變的是第三個參數。即你的站點中的任何頁面或資源,前兩個參數永遠固定,只須要改變第三個參數。前端

顯然你不想每次調用時都手動敲一下前兩個參數,麻煩不說,還容易出錯。怎麼辦呢?你會想直接將庫函數改爲單參不就好了?java

function simpleURL(path) {
    return "http://www.jackzxl.net/" + path;
}
複製代碼

這樣改有兩個問題,首先若是該庫函數還須要被其餘人或其餘地方使用,直接改庫函數這條路是絕對行不通的。其次就算你對函數有絕對的控制權,這樣改顯得也很是的不靈活,若是哪天你的站點要加上SSL呢?總不能把第一個參數再放回去吧。所以你正確的選擇是柯里化。後端

所謂柯里化就是:將函數與其參數的一個子集綁定起來後返回個新函數。若是感受比較抽象,能夠作一些類比,好比C++模板裏的偏特化,這樣理解起來能容易點。將上例柯里化一下:瀏覽器

var myURL = simpleURL.bind(null, 'http', 'www.jackzxl.net');
myURL('myfile.js');     //http://www.jackzxl.net/myfile.js

//站點加上SSL
var mySslURL = simpleURL.bind(null, 'https', 'www.jackzxl.net');
mySslURL('myfile.js');  //https://www.jackzxl.net/myfile.js
複製代碼

上述代碼用bind來實現柯里化。再回過頭體會一下柯里化定義:將函數與其參數的一個子集綁定起來後返回個新函數。柯里化後發現函數變得更靈活,更流暢,是一種簡潔的實現函數委託的方式app

爲什麼用bind來實現柯里化呢?由於簡單嘛,有現成的就沒必要本身造輪子了。但由於本篇介紹的是柯里化,因此咱們本身實現一下柯里化,來加深理解。它須要知足兩點:參數子集,返回新函數:dom

var currying = function(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        return fn.apply(null, newArgs);
    };
};

var myURL2 = currying(simpleURL, 'https', 'www.jackzxl.net');
myURL2('myfile.js');    //http://www.jackzxl.net/myfile.js
複製代碼

效果和用bind是同樣的,咱們仔細分析一下自定義的currying函數,首先參數fn是須要柯里化的simpleURL函數,後面均爲可變參數(函數的arguments可參考這裏),currying裏每行代碼的執行結果以下:異步

var currying = function(fn) {
    var args = [].slice.call(arguments, 1);
    //args爲["https", "www.jackzxl.net"]

    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        //newArgs爲["https", "www.jackzxl.net", "myFile.js"]

        return fn.apply(null, newArgs);
        //至關於return simpleURL("https", "www.jackzxl.net", "myFile.js");
    };
};
複製代碼

上面已經說明了柯里化的原理和實現。那究竟柯里化有什麼做用呢?常見的做用是:函數

  • 參數複用
  • 延遲運行
  • 扁平化

參數複用上面例子已經展現了,不贅述。

延遲運行其實很是直觀,由於不是返回運算結果,而是返回新函數,固然是延遲運行啦。例如bind就是延遲執行的表明,不贅述

扁平化的函數更加易讀。例如你要從站點的JSON數據裏獲取全部文章的title:

//JSON數據
{
    "user": "Jack",
    "posts": [
        { "title": "JavaScript Curry", "contents": "..." },
        { "title": " JavaScript Function", "contents": "..." }
    ]
}

//從JSON數據中獲取全部文章的title
fetchFromServer()
    .then(JSON.parse)
    .then(function(data){ return data.posts })
    .then(function(posts){
        return posts.map(function(post){ return post.title })
    })
複製代碼

固然你可能寫出更優雅的代碼…但這不是重點。重點是用柯里化將代碼更加易讀易維護:

var curry = require('curry');
var get = curry(function(property, object){ return object[property] });

fetchFromServer()
    .then(JSON.parse)
    .then(get('posts'))
    .then(map(get('title')))
複製代碼

提早返回?

最後網上還有個做用是提早返回,例如IE的事件和其餘瀏覽器不一樣,爲實現兼容性,能夠這樣實現:

function addHandler(target, eventType, handler){
    if (target.addEventListener){
        target.addEventListener(eventType, handler, false);
    } else {        //IE
        target.attachEvent("on" + eventType, handler);
    }
}
複製代碼

但上面這樣有個問題,每次調用addHandler函數都要進行一次if…else的判斷。常識告訴咱們,除非用戶在執行過程當中更換瀏覽器(若是能現實的話),不然只須要在用戶第一次鏈接站點時斷定一次便可,以後的調用沒必要再次檢查了。

用柯里化返回新函數的特性能夠實現:

var addEvent = (function(){
    if (target.addEventListener) {
        return function(target, eventType, handler) {
            target.addEventListener(eventType, handler, false);
        };
    } else {        //IE
        return function(target, eventType, handler) {
            target.attachEvent("on" + eventType, handler);
        };
    }
})();   
複製代碼

但在我看來,這裏用柯里化意義不大。由於柯里化雖然優勢不少,缺點一樣明顯,就是學習成本有點高。用柯里化實現「提早返回」,維護的成本大於收益。

不用柯里化怎麼實現呢?一個三元運算符就搞定了:

var addHandler = document.body.addEventListener ?
    function(target, eventType, handler){
        target.addEventListener(eventType, handler, false);
    } :
    function(target, eventType, handler){
        target.attachEvent("on" + eventType, handler);
    };
複製代碼

或者函數內部重寫該函數也行:

function addHandler(target, eventType, handler){
    if (target.addEventListener){
        addHandler = function(target, eventType, handler){  //重寫該函數
            target.addEventListener(eventType, handler, false);
        };
    } else {        //IE
        addHandler = function(target, eventType, handler){  //重寫該函數
            target.attachEvent("on" + eventType, handler);
        };
    }
    addHandler(target, eventType, handler); //調用新函數
}
複製代碼

兩種方法都很是直觀,簡單明瞭,不要爲了用柯里化而用柯里化。

總結

柯里化雖然有一個神祕的名字,但其實說穿了並不神祕。在前端它的應用場並很少(固然也可能我經驗比較淺),更多的應該是用在後端異步函數裏,如Node.js,對於異步API用柯里化能夠減小回調嵌套。

https://www.jianshu.com/p/9b6b5c7527fc

相關文章
相關標籤/搜索