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