JavaScript函数柯里化

概述

函数式编程中的一个重要概念就是函数柯里化(Currying),虽然函数式编程目前在JavaScript社区还不是主流,但是JavaScript中函数天生就是一等公民(一类函数),所以完全可以按照函数式编程的思想编程。

一般函数

JavaScript中的一般函数的实现。

1
2
3
const plus = (x, y, z, m) => return x + y + z + m;

console.log(plus(1, 2, 3, 4)); // 10

函数柯里化及其实现

标准的柯里化不是将一个函数无限返回返回函数并连接调用,如下面这种情形。

1
2
3
4
5
6
7
const plus = (x) => {
let result = (y) => plus(x + y);
result.valueOf = () => x;
return result;
};
console.log(plus(1)(2)(3)(4)); // 10
console.log(plus(1)(2)(3)(4)(5)); // 15

这种模式并非柯里化,只是一种递归自调用而已。注意这里的返回值,值虽然是对的,但是返回值得类型永远是函数类型,因为我们是通过覆盖Object.prototype.valueOf来实现的。

JavaScript中真正的函数柯里化应该是使原函数参数数量,参数对应位置表示的意义都是不变的,固定的。

以下是一种手动的实现方式。

1
2
3
const plus = x => y => z => m => x + y + z + m;

console.log(plus(1)(2)(3)(4)); // 10

以下是能将任何一个常规函数进行自动化柯里化的函数,生成后的函数和原函数的功能一致,只是改变了调用方式。面试中一般以能答出这种回答最佳。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict'
const currying = function (targetFn, targetFnLength = targetFn.length) {
return function () {
const args = Array.prototype.slice.apply(arguments);
if (targetFnLength <= args.length) {
return targetFn.apply(null, args);
}
return currying(function () {
const followingArgs = Array.prototype.slice.apply(arguments);
return targetFn.apply(null, args.concat(followingArgs));
}, targetFnLength - args.length);
}
}
const curry = fn => currying(fn);
const plus = curry((x, y, z, m) => {
return x + y + z + m;
});
console.log(plus(1, 2, 3, 4)); // 10
console.log(plus(1, 2, 3)(4)); // 10
console.log(plus(1, 2)(3)(4)); // 10
console.log(plus(1)(2)(3)(4)); // 10

尾调用优化

在ES6的标准中,JavaScript已经实现了尾调用优化。

所谓的尾调用优化,就是值当一个函数的最后一个语句是调用另一个函数时,就不需要维护当前函数的调用记录,从而节省资源,这个可以在编译器层面就节省资源。

如果在ES6环境下(支持尾调用优化的环境,以前的Babel也是支持的,不过当前版本正在重写尾调用的优化,暂时不支持,以后肯定会支持),柯里化的正好符合这样一种情况,可以看到上面的柯里化函数的执行的最后一句都是调用自身函数或者另一个函数而没有其他操作。

参考

  1. Currying
  2. Web前端面试题,求解答? - 第七页的回答 - 知乎
  3. 尾调用优化 - 阮一峰的网络日志