简单理解JavaScript中的柯里化和反柯里化

就像最早听到斐波拉切数列一样,第一次听到柯里化我也是懵逼的

前言

本文旨在让大家简单理解柯里化和反柯里化,这里不做深入探究,只求能带大家装逼就好,看完还不懂你砍我。

我们先来简单了解一下他们的作用。

柯里化又称部分求值,字面意思就是不会立刻求值,而是到了需要的时候再去求值。如果看的懵逼,没事,看完整篇文章再回过头来看这里你就会豁然开朗。

反柯里化的作用是,当我们调用某个方法,不用考虑这个对象在被设计时,是否拥有这个方法,只要这个方法适用于它,我们就可以对这个对象使用它。

柯里化(curring)

我们有这样一个场景,记录程序员一个月的加班总时间,那么好,我们首先要做的是记录程序员每天加班的时间,然后把一个月中每天的加班的时间相加,就得到了一个月的加班总时间。

但问题来了,我们有很多种方法可以实现它,比如最简单的:

1
2
3
4
5
6
7
8
9
10
11
12
var monthTime = 0;
function overtime(time) {
return monthTime += time;
}
overtime(3.5); // 第一天
overtime(4.5); // 第二天
overtime(2.1); // 第三天
//...
console.log(monthTime); // 10.1

每次传入加班时间都进行累加,这样当然没问题,但你知道,如果数据量很大的情况下,这样会大大牺牲性能。

那怎么办?这就是柯里化要解决的问题。

其实我们不必每天都计算加班时间,只需要保存好每天的加班时间,在月底时计算这个月总共的加班时间,所以,其实只需要在月底计算一次就行。

下面的overtime函数还不是一个柯里化函数的完整实现,但可以帮助我们了解其核心思想:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var overtime = (function() {
var args = [];
return function() {
if(arguments.length === 0) {
var time = 0;
for (var i = 0, l = args.length; i < l; i++) {
time += args[i];
}
return time;
}else {
[].push.apply(args, arguments);
}
}
})();
overtime(3.5); // 第一天
overtime(4.5); // 第二天
overtime(2.1); // 第三天
//...
console.log( overtime() ); // 10.1

柯里化的核心思想就是这样,看到这里你肯定已经懂了,至于真正的柯里化函数,网上有很多,大家可以去Google一下。

反柯里化(uncurring)

反柯里化的的作用已经在前言说过了,这里讲下它的由来。

2011年JavaScript之父Brendan Eich发表了一篇Twitter,提出了反柯里化这个思想,下面这段代码是反柯里化的实现方式之一:

1
2
3
4
5
6
7
Function.prototype.uncurring = function() {
var self = this;
return function() {
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj, arguments);
};
};

我们先来看看上面这段代码有什么作用。

我们要把Array.prototype.push方法转换成一个通用的push函数,只需要这样做:

1
2
3
4
5
6
7
var push = Array.prototype.push.uncurring();
//测试一下
(function() {
push(arguments, 4);
console.log(arguments); //[1, 2, 3, 4]
})(1, 2, 3)

arguments本来是没有push方法的,通常,我们都需要用Array.prototype.push.call来实现push方法,但现在,直接调用push函数,既简洁又意图明了。

就和前言写的那样,我们不用考虑对象是否拥有这个方法,只要它适用于这个方法,那就可以使用这个方法(类似于鸭子类型)。

我们来分析一下调用Array.prototype.push.uncurring()这句代码时,发生了什么事情:

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
Function.prototype.uncurring = function() {
var self = this; //self此时是Array.prototype.push
return function() {
var obj = Array.prototype.shift.call(arguments);
//obj 是{
// "length": 1,
// "0": 1
//}
//arguments的第一个对象被截去(也就是调用push方法的对象),剩下[2]
return self.apply(obj, arguments);
//相当于Array.prototype.push.apply(obj, 2);
};
};
//测试一下
var push = Array.prototype.push.uncurring();
var obj = {
"length": 1,
"0" : 1
};
push(obj, 2);
console.log( obj ); //{0: 1,1: 2, length: 2 }

看到这里你应该对柯里化和反柯里化有了一个初步的认识了,但要熟练的运用在开发中,还需要我们更深入的去了解它们内在的含义。

本文参考:

  1. JavaScript设计模式与开发实践

  2. Currying in JavaScript

  3. Curried JavaScript functions

本文作者:余震(Freak)
本文出处:Rockjins Blog
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN许可协议。转载请注明出处!

坚持,您的支持将鼓励我继续爬下去!