Currying柯里化是函式式語言都有的一個特性,如Perl,Python,JavaScript。本篇就借用一下JavaScript,介紹一下柯里化的思想及應用。
假設函式庫裡提供這樣一個拼接URL地址的函式:
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,唯一需要改變的是第三個引數。即你的站點中的任何頁面或資源,前兩個引數永遠固定,只需要改變第三個引數。
顯然你不想每次呼叫時都手動敲一下前兩個引數,麻煩不說,還容易出錯。怎麼辦呢?你會想直接將庫函式改成單參不就行了?
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來實現柯里化。再回過頭體會一下柯里化定義:將函式與其引數的一個子集繫結起來後返回個新函式。柯里化後發現函式變得更靈活,更流暢,是一種簡潔的實現函式委託的方式
為何用bind來實現柯里化呢?因為簡單嘛,有現成的就不必自己造輪子了。但因為本篇介紹的是柯里化,所以我們自己實現一下柯里化,來加深理解。它需要滿足兩點:引數子集,返回新函式:
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