这是我参与更文挑战的第24天,活动详情查看: 更文挑战
前言
维基百科:在计算机科学中,柯里化(Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
科里化面试题
在前端面试中有一个关于柯里化的面试题,流传甚广。
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6
add(1, 2, 3)(4) = 10
add(1)(2)(3)(4)(5) = 15
很明显,计算结果正是所有参数的和,add方法每运行一次,肯定返回了一个同样的函数,继续计算剩下的参数。
1. 如果只调用2次
function add(a) {
return function(b) {
return a + b;
}
}
console.log(add(1)(2)); // 3
2. 如果只调用3次
function add(a) {
return function(b) {
return function (c) {
return a + b + c;
}
}
}
console.log(add(1)(2)(3)); // 6
3. 如果调用的次数不确定?
其实上面的做法都是利用闭包的特性,将所有的参数,集中到最后返回的函数里进行计算并返回结果。因此我们在封装时,主要的目的,就是将参数集中起来计算。可以这样写个小结:
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = [].slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var adder = function () {
var _adder = function() {
[].push.apply(_args, [].slice.call(arguments));
return _adder;
};
// 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
return adder.apply(null, [].slice.call(arguments));
}
// 输出结果,可自由组合的参数
console.log(add(1, 2, 3, 4, 5)); // 15
console.log(add(1, 2, 3, 4)(5)); // 15
console.log(add(1)(2)(3)(4)(5)); // 15
利用闭包的特性通过一些方法将所有的参数收集在一个数组里,并在最终隐式转换时将数组里的所有项加起来。因此我们在调用add方法的时候,参数就显得非常灵活。
函数科里化
函数柯里化就是创建已经设置单个参数或者多个参数的函数,函数变为接受一个参数,返回一个值,柯里化的用途主要是参数复用。再举一个 demo :
// 参数复用
function add(a, b) {
return a + b;
}
add(1,2) //3
// 柯里化之后可以这样
var addCurry = curry(add);
addCurry(1)(2); //3
或许针对这种简单的将两个数相加的场景,柯里化显得有点多余。但是如果我们想使用这个函数完成通用的事情,比如为所有的数加5,就可以使用addCurry(5)(x),使得将两个数相加的函数有了通用性。
通用版
var curry = function(func){
var args = [].slice.call(arguments,1);
return function(){
var newArgs = args.concat([].slice.call(arguments));
return func.apply(this,newArgs);
}
}
首先将参数进行分割,也就是将除了func之外的参数存进args。返回的函数接受新传入的参数并与之前的参数合并,从而将所有的参数传入函数中,并执行真正的函数。
改进版
比如说add这个函数接受两个参数,那么针对柯里化之后的函数,若传入的参数没有到达两个的话,就继续调用curry,继续接受参数。若参数到达2个了,就直接调用add函数。
var curry = function(func,args){
var length = func.length;
args = args||[];
return function(){
newArgs = args.concat([].slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,func,newArgs);
} else {
return func.apply(this,newArgs);
}
}
}
var addCurry = curry(add);
addCurry(1,2) //3
addCurry(1)(2) //3
进阶版
但这一版柯里化函数仍然不能完全满足要求,因为它只针对有特定参数个数的函数适用。再回到 前面的那道面试题,题目要求不变。
add(1)(2)(3) = 6
add(1, 2, 3)(4) = 10
add(1)(2)(3)(4)(5) = 15
前面写的已经不能满足需求,这里我们改用函数的 toString 来完成。
当我们返回函数的时候,会调用函数的toString来完成隐式转换,这样输出的就不是函数的字符串形式而是我们定义的toString返回的值。这样就既可以保持返回一个函数,又能够得到一个特定的值。
function add(){
// 第一次执行时,定义一个数组专门用来储存所有的参数
var args = [].slice.call(arguments);
// 在内部声明一个函数,
// 利用闭包的特性保存_args并收集所有的参数值
var fn = function(){
var newArgs = args.concat([].slice.call(arguments));
return add.apply(null,newArgs);
}
// 利用隐式转换的特性,
// 当最后执行时隐式转换,并计算最终的值返回
fn.toString = function(){
return args.reduce(function(a, b) {
return a + b;
})
}
return fn ;
}
// 可以接受任意个数的参数
add(1)(2,3) //6
add(1)(2)(3)(4)(5) //15
真正的函数柯里化
无论是上面的面试题还是demo,我们从中不难看出,柯里化是将接受多个参数转换为接受一个单一参数。简单梳理一下:
假设你有一个储钱罐 countMoney 函数,和一个记录本 arr 数组,当你每月有空钱时进行储存,每次在 arr 中记录一次,存入储钱罐中:
var arr=[];
var countMoney=function(arr){
var sum=0;
for(var i=0;i<arr.length;i++){
sum+=arr[i];
}
return sum;
}
arr.push(1);
arr.push(2);
countMoney(arr);
可以通过这种方式来进行存储,但是有本记录,是会被发现的,所以这个时候可以这样:
// 每次存储是调用一次,不需要再次记录下来
countMoney(1);
countMoney(2);
// 等到真正需要的时候我们可以直接计算出来这个总值
countMoney(); //3
于是问题解决的方式变为柯里化问题,需要将多个参数接受转换为接受单一参数的问题。于是我们可以使用下面的方式进行处理。
var countMoney = (function() {
let moneys = 0;
let arr = [];
var result = function() {
// 判断是否还有参数,如果没有,则返回存储起来值的总和
if(arguments.length == 0) {
for(var i = 0; i < arr.length; i++) {
money += arr[i];
}
return money;
} else {
// arguments 是个类数组来着,应该用展开符展开才能push进去
// 通过arguments 处理可以传入多个参数值
console.log(...arguments)
arr.push(...arguments);
return result;
}
}
return result;
})();
countMoney(1)(2)(3)
countMoney(6)
上面的例子完全可以实现柯里化,并且进行扩展,现在可以安全的存放钱了。
总结
柯里化(Currying),又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。
接收单一参数,因为要携带不少信息,因此常常以回调函数的理由来解决。将部分参数通过回调函数等方式传入函数中返回一个新函数,用于处理所有的想要传入的参数。
实际上,在JavaScript的很多思想和设计模式中,闭包是个很常见的且很重要的东西,上面两个例子代码中,本质上就是利用了闭包。上面的 countMoney 函数是个立即执行的函数,返回一个新函数,而这个新函数实际上就是一个闭包,这个新函数把每次接收到的参数都存储起来,并且继续返回一个新函数,当发现某次调用时没有传入参数,那就意味着要进行数据统计,从而把之前存储的数据一次拿出来计算,最后返回计算结果。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!