函数柯里化

函数式编程在把函数当作一等公民的同时,就不可避免的会产生“柯里化”这种用法。
函数柯里化是逐步传参,逐步缩小函数的适用范围,逐步求解的过程。

1
2
3
4
5
6
7
8
9
10
11
// 通用的柯里化函数, 实现把所有参数传递给 fn
var currying = function(fn) {
var _args = []; // 用来保存所有的参数
return function _fn (...args) {
// 当没有参数时,传入收集后的参数执行 fn
if (args.length === 0) return fn.apply(this, _args);
// 当有参数时,进行参数的收集,并返回函数自身
_args.push(...args);
return _fn;
};
};

推迟计算

柯里化可以推迟计算,需要的时候再去求值。比如一家店要计算每月的盈利,首先要记录每天的盈利,这个就可以使用柯里化函数去解决。

1
2
3
4
5
6
var multi = (...args) => args.reduce((total, cur) => total + cur, 0);
var sum = currying(multi);
sum(100, 200)(300);
sum(400);
console.log(sum()); // 1000
console.log(sum(3)(30)(300)()); // 1333

复用参数

柯里化还可以复用参数。比如五千元买入不同的股票,计算三个月后各自的总价值。实现参数复用只需要将 curring 函数修改一行代码。

1
2
3
4
5
6
7
8
var currying2 = function(fn) {
var _args = [].slice.call(arguments, 1); // 修改了此行
return function() {
if (arguments.length === 0) return fn.apply(this, _args);
[].push.apply(_args, [].slice.call(arguments));
return arguments.callee;
};
};
1
2
3
var sum2 = currying2(multi, 5000); // 复用了初始投资这个参数
console.log(sum2(45, 33, 90)()); // 5168
console.log(sum2(13, 40, 120)()); // 5173

提前返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
});
}
};

以上代码,我们每次使用addEvent为元素添加事件的时候,IE6/IE7都会走一遍if...else if...,其实使用柯里化只要一次判定就可以了,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
});
};
}
})();

不定参数柯里化

文章头部的currying函数已经是不定参数的柯里化了,但是最后需要执行()才能结束

1
sum(100, 200)(300)(); // 600

现在希望实现如下效果

1
sum(100, 200)(300); // 600

此时的柯里化函数应该如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function curry(fn) {
// 保存预置参数
const presetArgs = [].slice.call(arguments, 1)
// 返回一个新函数
function curried () {
// 新函数调用时会继续传参
const restArgs = [].slice.call(arguments)
const allArgs = [...presetArgs, ...restArgs]
return curry.call(null, fn, ...allArgs)
}
// 重写toString
curried.toString = function() {
return fn.apply(null, presetArgs)
}
return curried;
}

通过改写函数的toString方法,使得结果函数可以被Javascript引擎解析,得到一个预期的值。