js中的柯里化(currying)

什么是currying?

维基百科是这么解释的:

在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的,尽管它是Moses Schönfinkel和戈特洛布·弗雷格发明的。

从字面上看,说了一堆确实不知道是什么意思。其实柯里化就是把一个多参数的函数,转化成一个接受部分参数,从而返回一个接受剩余参数的函数。
举个🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
//currying之前
var add = function (x, y) {
return x + y;
}
add(1,2)
//currying之后
var add = function (x) {
return function (y) {
return x + y;
}
}
add(1)(2)

单从这个🌰来看,确实是看不出currying的好处,反而觉得多此一举,那下面让我们看下为什么要柯里化,柯里化的好处是什么?

  1. 参数复用
    从上面的例子来看,其实就能看出来了,被currying后的函数add调用时,add(1)(2) //3
    add(1)(3) //4,这个参数1其实就复用了,这也是javascript中一个老生常谈的话题闭包。利用闭包的特性记住参数1,从而复用。
  2. 动态生成函数
    正常的如果封装一个绑定事件的兼容写法,我们会这么写:
1
2
3
4
5
6
7
8
9
10
11
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);
});
}
};

函数本身没问题,但问题在于调用函数的时候,每次都需要判断一次,绑定十次,判断十次 。那如果能判断一次,让后续绑定调用函数的时候都不用在判断了呢,就是柯里化。

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

柯里化后的写法,调用这个addEvent会生成一个函数,这个函数是根据不同的浏览器兼容,返回一个动态的函数,这样的写法只有初始调用的时候判断一次,后续的绑定事件就不用再去判断了。

3 . 延迟执行

1
2
3
4
5
6
7
Function.prototype.bind = function(context) {
var self = this;
var args = Array.prototype.slice.call(arguments);
return function() {
return self.apply(context, args.slice(1));
}
};

其实bind方法就是利用函数柯里化返回一个延迟执行的改变上下文的函数。(函数主体本身不执行,与call/apply直接执行并改变不同,本质上就是延迟执行)。

最后我们用一道题目来结束这个话题。

1
2
3
4
5
6
7
8
9
10
11
12
13
function sum(a, b, c) {
return a + b + c;
}
function currying() {
// TODO
}
var curry = currying(sum)
console.log(curry(1)(2)(3)) //6
console.log(curry(1,2)(3)) //6
console.log(curry()(1)(2)(3)) //6
console.log(curry(1,2,3)) //6

试着尝试实现下这个currying函数,我也会给出自己的实现。

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
// es5写法
function currying(fn) {
var args = [].slice.call(arguments, 1);
if (args.length < fn.length){
var f = function(){
var _args = [].concat.apply(args, arguments);
_args.unshift(fn)
return currying.apply(null, _args);
}
return f;
}
return fn.apply(null, args)
}
// es6写法
function currying(fn, ...rest) {
if (rest.length < fn.length){
const f = (...args) => {
const _args = rest.concat(args);
return currying(fn, ..._args);
}
return f;
}
return fn.call(null, ...rest)
}

最后告诉大家一个小秘密,其实函数柯里化最大的作用就是面试

菜鸟学习笔记,如有不对,还希望高手指点。如有造成误解,还希望多多谅解。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。