函數柯里化是所有編程語言推崇的函數優化方式,js的函數柯里化是你寫出優雅函數的基礎。
概念
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數且返回結果的新函數的技術。
例如:我們求(a+b)*c 如果普通方法實現 f(a,b,c),用柯里化方法:f(a)(b)©這種調用格式獲取。
實現原理
- 閉包來保存參數;
- 高階函數實現運算;
例如最簡單的例子:求 (15+3)*4的值
let calcu = (a, b, c) => (a+b)*c;
function curry(fn, args){
let len = fn.length;
let _this = this;
let _args = args || [];
return function(){
let args = Array.prototype.slice.apply(arguments);
args = Array.prototype.concat.call(_args, args);
// 當接收到的參數小於fn所需參數個數時,繼續接收參數
if(args.length < len){
return curry.call(_this, fn, args);
}
return fn.apply(this, args);
}
}
let add = curry(calcu);
console.log(add(15)(3)(5)); //72
在上面的代碼中,我們使用curry函數它可以不用一次性接收三個參數,而是慢慢接收,當發現接收到的參數達到3個之後再返回結果。這就是參數複用(通過例子我們可以看到,實現這個特點主要原因是利用了閉包,將接收到的參數存於_args中,由於閉包的原因這些參數在函數執行完之後並不會被釋放掉。
上面的curry方法,將每次調用fn時讀入的參數用args來保存,並將本次讀入的參數args與當前一共擁有的所有參數_args用concat方法連接起來,當參數個數符合fn的參數個數要求時,則調用fn。
特點
- 參數複用: 就是利用閉包的原理,讓我們前面傳輸過來的參數不要被釋放掉。
- 提前確認: 利用柯里化特性對某些全局方法進行改寫,這樣其實就是提前確定了會走哪一個方法,避免每次都進行判斷。
- 延遲運行:利用閉包將函數的定義和執行環境分開。
參數複用案例:
// 正常正則驗證字符串 reg.test(txt)
// 函數封裝後
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true
// Currying後
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
提前確認:
var on = function(isSupport, element, event, handler) {
isSupport = isSupport || document.addEventListener;
if (isSupport) {
return element.addEventListener(event, handler, false);
} else {
return element.attachEvent('on' + event, handler);
}
}
延遲運行:
Function.prototype.bind = function (context) {
var _this = this
var args = Array.prototype.slice.call(arguments, 1)
return function() {
return _this.apply(context, args)
}
}
柯里化函數的最佳實現
函數柯里化是一個比較難以應用的方法,最主要是能夠理解其原理使你閱讀源碼無障礙,下面給出一個柯里化的最佳實現方法,背過他可以救急
// 支持多參數傳遞
function progressCurrying(fn, args) {
var _this = this
var len = fn.length;
var args = args || [];
return function() {
var _args = Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 如果參數個數小於最初的fn.length,則遞歸調用,繼續收集參數
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 參數收集完畢,則執行fn
return fn.apply(this, _args);
}
}