1. 什么是函数柯里化?
固定某些参数,将接受多个参数的函数变换成接受单一参数的函数。
2. 应用
2.1 参数复用
例1:在一款游戏app下,判断用户的年龄是否大于18
let limit=(levle,age)=>{
return age>level
}
// 调用时:
limit(18,19)
limit(18,17)
limit(18,16)
limit(18,20)
···
柯里化后:
let limit=(level)=>{
return (age)=>{
age>level
}
}
调用时:
let limitChina=limit(18)
limitChina(21)
limitChina(17)
let limitAmerica=limit(20)
limitChina(21)
limitChina(17)
柯里化前,每次都重复传值18,在比较复杂的业务下,传参比较多,逻辑比较复杂时,会造成代码冗余。
柯里化后,固定了某些参数,得到一个子函数,该子函数可以在leval=18业务下所有的页面使用。且调用多次不用传递重复的参数。如果业务拓展到了国外,只需要传递不同的level,可以得到一个新的子函数,可以在这套业务下的所有页面使用。
好处: 固定了参数,形成子函数,提高复用。避免了参数过多的代码冗余。
例2:利用柯里化封装正则验证
// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
// 即使是相同的正则表达式,也需要重新传递一次
console.log(check(/\d+/g, 'test1')); // true
console.log(check(/\d+/g, 'testtest')); // false
console.log(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)
console.log(hasNumber('test1')); // true
console.log(hasNumber('testtest')); // false
console.log(hasLetter('21212')); // false
2.2 提前确认
/**
*
* @param {要绑定事件的 DOM 元素} element
* @param {绑定什么事件} event
* @param {事件处理函数} handler
*/
var on = function (element, event, handler) {
if (document.addEventListener) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
} else {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
}
}
on(div, 'click', function(){})
var on = (function () {
if (document.addEventListener) {
return function (element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function (element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
on(div, 'click', function(){})
//换一种写法可能比较好理解一点,上面就是把 isSupport 这个参数给先确定下来了
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);
}
}
on(true, div, 'click', function(){})
on(true, div, 'click', function(){})
on(true, div, 'click', function(){})
相对于第一种方法,第二种方法自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
3. 柯里化面试题
1.实现一个通用的柯里化函数,参数不变,可通过任意次数传入
function curry() {
var fn = arguments[0]; // 获取要执行的函数
var args = [].slice.call(arguments, 1); // 获取传递的参数,构成一个参数数组
// 如果传递的参数已经等于执行函数所需的参数数量
if (args.length === fn.length) {
return fn.apply(this, args)
}
// 参数不够向外界返回的函数
function _curry(){
// 推入之前判断
// 将新接收到的参数推入到参数数组中
args.push(...arguments);
if(args.length === fn.length){
return fn.apply(this, args)
}
return _curry;
}
return _curry;
}
验证:
// 测试 1
function add(a, b, c) {
return a + b + c;
}
console.log(curry(add)(1)(2)(3)); // 6
console.log(curry(add, 1)(2)(3)); // 6
console.log(curry(add, 1, 2, 3)); // 6
console.log(curry(add, 1)(3, 4)); // 8
var addCurrying = curry(add)(2);
console.log(addCurrying(7)(8)); // 17
// 测试 2
function check(reg, txt) {
return reg.test(txt)
}
var hasNumber = curry(check)(/\d+/g);
console.log(hasNumber('test1'));// true
2.实现一个add,使得计算结果满足如下预期
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存 _args 并收集所有的参数值
var _adder = function () {
_args.push(...arguments);
return _adder;
};
// 这个是最后输出的时候被调用的,return 后面如果是函数体,
// 为了输出函数体字符串会自动调用 toString 方法
// 利用 toString 隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
// 这个 return 是第一次调用的时候返回上面的函数体,
// 这样后面所有的括号再执行的时候就是执行 _adder 函数体
return _adder;
}
console.log(add(1)(2)(3).toString()); // 6
console.log(add(1, 2, 3)(4).toString()); // 10
console.log(add(1)(2)(3)(4)(5).toString()); // 15
console.log(add(2, 6)(1).toString()); // 9