JS - 函数柯里化

一、概念

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数。

简单来说,柯里化是一种函数的转换,它是指将一个函数从可调用的 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

参考链接:
前端基础进阶(十):深入详解函数的柯里化
「前端进阶」彻底弄懂函数柯里化
柯里化(Currying)

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值