手写原理 | 柯里化

柯里化

柯里化:是一种函数的转化,它是指将接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术

简单地说,就是固定一个参数,返回一个接受剩余参数的函数,实质上就是使用闭包返回一个延迟执行函数

看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function curry(f) {   // curry(f)执行柯里化转换
return function(a) {
return function(b) {
return f(a,b)
};
};
}

// 用法
function sum(a,b) {
return a+b;
}

let curriedSum = curry(sum);
/*
curriedSum = function(a) {
return function(b) {
return sum(a,b);
}
}
*/

let res = curriedSum(1)(2); // 3
/*
分解
curriedSum(1) = function(b) {
return sum(1,b)
}

curriedSum(2) = sum(1,2); // 3
*/

curry的实现如上面代码所示,十分简单:只要有两个包装器(wrapper)

  • curry(func) 的结果就是一个包装器 function(a)
  • 当它被像 curriedSum(1) 这样调用时,它的参数会被保存在词法环境中,然后返回一个新的包装器 function(b)
  • 然后这个包装器被以 2 为参数调用,并且,它将该调用传递给原始的 sum 函数。

柯里化优点

参数复用,或者说是固定参数,避免重复传参

比如说我们用正则验证一个手机号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function curry(fn, ...args) {
return (...callbackArgs) => {
const currentArgs = [...args, ...callbackArgs];
return callbackArgs.length === 0 || currentArgs.length === fn.length ? fn(...currentArgs) : curry(fn, ...currentArgs);
}
}

const phoneReg = /^1[3-9]\d{9}$/;

function _checkPhone(reg, phone) {
return reg.test(phone);
}

console.log(_checkPhone(phoneReg, 19956526362));

// 柯里化
const checkPhone = curry(_checkPhone)(phoneReg); // 这样我们就复用了验证手机的正则,这就是复用参数,或者说是固定参数
checkPhone(19956526362);
checkPhone(16956526362);

提前返回,或者说是提前确认,避免重复判断 和 延迟执行

再做一个拓展,我们需要对一个正确的手机号做一系列不同步的操作(同步的话就没有意义了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function doSomething1(reg, phone, callback) {
reg.test(phone) && callback();
}

function doSomething2(reg, phone, callback) {
reg.test(phone) && callback();
}

doSomething1(phoneReg, 19956526362,callback1);
doSomething2(phoneReg, 19956526362,callback2);
// 既然是对同一个号码做判断,我们当然可以先将判断结果保存下来,这样就不用每次都做判断了
function _doSomething(reg, phone, callback) {
reg.test(phone) && callback();
}

const doSomething = curry(_doSomething)(19956526362); // 这里就是提前返回电话号码是否正确了
doSomething(callback1); // 这里就是延迟执行
doSomething(callback2);

动态创建函数

1
2
3
4
5
6
7
8
9
10
11
const addEvent = (function () {
if (window.addEventListener) {
return (elem, type, fn, capture) => {
elem.addEventListener(type, (e) => fn.call(elem, e), capture);
};
} else {
return (elem, type, fn, capture) => {
elem.attachEvent('on' + type, (e) => fn.call(elem, e));
};
}
})();

手写实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function myCurry(func, ...args) {   // func为需要柯里化的函数
return function() {
// 合并参数
args = [...args, ...arguments];
// 判断参数的个数是否足够
if (args.length < func.length) {
// 不够继续递归
// 注意这里每一次递归都会形成新的闭包
// 保证柯里化函数每一步都是独立的 互不影响
return myCurry(func, ...args);
} else {
// 参数足够,执行函数
return func(...args);
}
};
};


// test
function sum(a,b,c) {
return a+b+c;
}

let sumCurried = myCurry(sum);
console.log(sumCurried(1)(2)(3)); // 6

let sumCurried = myCurry(sum);
console.log(sumCurried(1,2)(3)); // 6

let sumCurried = myCurry(sum);
console.log(sumCurried(1)(2,3)); // 6

参考

柯里化(Currying)

看完这个,你还不懂函数柯里化?