一、概念
柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数。
简单来说,柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c)
转换为可调用的 f(a)(b)(c)
。
柯里化的3个常见作用:
- 参数复用
- 提前返回
- 延迟计算/运行
二、参数复用
参数复用可以用在各种需要正则检验的需求,比如校验电话号码、校验邮箱、校验身份证号、校验密码等。
这时我们会封装一个通用函数 checkByRegExp ,接收两个参数,校验的正则对象和待校验的字符串。
普通方法,如果需要多次校验那么就调用多次方法
function checkByRegExp(regExp,string) {
return regExp.test(string);
}
checkByRegExp(/^1\d{10}$/, '12345678900'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '12345678900');
checkByRegExp(/^1\d{10}$/, '12345678900');
柯里化写法
//进行柯里化
let _check = curry(checkByRegExp);
//生成工具函数,验证电话号码
let checkCellPhone = _check(/^1\d{10}$/);
checkCellPhone('12345678900'); // 校验电话号码
checkCellPhone('12345678900'); // 校验电话号码
checkCellPhone('12345678900'); // 校验电话号码
可以借助柯里化对 checkByRegExp 函数进行封装,以简化代码书写,提高代码可读性。
三、提前返回
关于提前返回,这里引用一个网上流传的例子:
兼容现代浏览器以及IE浏览器的事件添加方法。以下是正常写法。
var addEvent = function(el, type, fn, capture) {
if (window.addEventListener) {
el.addEventListener(type, function(e) {
fn.call(el, e);
}, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, function(e) {
fn.call(el, e);
});
}
};
这样会导致每次使用触发事件的时候,都会触发 if…else if …,如果使用柯里化,那么就只会在第一次的时候走 if…else if … ,即使之后再触发事件,也不会走条件判断。
var addEvent = (function(){
if (window.addEventListener) {
return function(el, sType, fn, capture) {
el.addEventListener(sType, function(e) {
fn.call(el, e);
}, (capture));
};
} else if (window.attachEvent) {
return function(el, sType, fn, capture) {
el.attachEvent("on" + sType, function(e) {
fn.call(el, e);
});
};
}
})();
四、延迟计算
关于延迟计算,一般情况下,函数在传入参数之后,就会立即执行,柯里化的延迟计算,就是能让函数即使传入参数,也不会立即实行,而是将参数先存储在一个数组中,等到某个时机再调用数组,将之前的参数一起计算。
var curryWeight = function(fn) {
var _selfWeight = [];
return function() {
if (arguments.length === 0) {
return fn.apply(null, _selfWeight);
} else {
_selfWeight = _selfWeight.concat([].slice.call(arguments));
}
}
};
var selfWeight = 0;
var addWeight = curryWeight(function() {
var i=0; len = arguments.length;
for (i; i<len; i+=1) {
selfWeight += arguments[i];
}
});
addWeight(2.3);
addWeight(6.5);
addWeight(1.2);
addWeight(2.5);
addWeight(); // 这里才计算
console.log(selfWeight); // 12.5
五、实现柯里化函数
实现柯里化函数可以通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数。
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6,仍然可以被正常调用
alert( curriedSum(1)(2,3) ); // 6,对第一个参数的柯里化
alert( curriedSum(1)(2)(3) ); // 6,全柯里化
六、柯里化面试题
这里再讲以下,网上的那一道和柯里化相关的面试题,以及一个关于函数内置的toString函数的知识点。
写一个函数实现
add(1)(2)(3) // 6
// 实现方法
var add = function(a) {
return function(b) {
return funciton(c) {
return a+b+c;
}
}
}
add(1)(2)(3);
上面这种写法是最简单的实现方法,但是缺点显而易见,只能实现单一一种情况,如果传入的参数不定,那么就无法使用这种方法,这里附上一种写法,可以适应多个参数的情况。
var add = function(a) {
var sum = a;
var addMore = function(b) {
sum += b;
return addMore;
};
addMore.toString = function() {
return sum;
};
return addMore;
};
var a = add(1)(2)(3)(4).toString(); //10
这里需要提一下,如果是单纯的,
var a = add(1)(2)(3)(4);
这样输出的是一个函数式
{ [Function: addMore] toString: [Function] }
必须得转化以下,才能够输出数字。另外,还有一种情况:
var add = function(a) {
var sum = a;
var addMore = function(b) {
sum += b;
return addMore;
};
addMore.toString = function() {
return sum;
};
return addMore;
};
var a = add(1)(2)(3)(4)+10; //20
这是由于函数的隐式转换。当我们直接将函数参与其他的计算时,函数会默认调用toString方法,直接将函数体转换为字符串参与计算。
但是,使用函数内置的 toString()函数 输出的是一个函数体字符串。例子如下,
function b() {
return 10;
}
var c = b+10;
console.log(c);
console.log(typeof(c));
// 输出
function b() {
return 10;
}10
string
所以,必须使用自定义的 toString 是函数返回我们想要的值。
也就是说,当没有定义toString时,函数的隐式转换会调用默认的toString方法,它会将函数的定义内容作为字符串返回。
而当我们自定义了toString方法时,那么隐式转换的返回结果则由我们自己控制了。
另外,vauleOf 的方法的作用和 toString 方法一样,但是valueOf会比toString后执行。
还有一个注意的点,
add (2,3) arguments.length == 2
add (2)(3) arguments.length == 1
柯里化面试题还有另一种形式
var a = add(1)(2)(3)(4); //10
var b = add(1, 2, 3, 4); //10
var c = add(1, 2)(3, 4); //10
var d = add(1, 2, 3)(4); //10
// 实现方法
function add() {
var _args = [].slice.call(arguments);
var adder = function () {
var _adder = function() {
_args.push(...arguments);
return _adder;
};
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
return adder(..._args);
}
var a = add(1)(2)(3)(4,5).toString();
console.log(a); // 10