本文最后更新于 2025年7月30日 下午
参考:https://juejin.cn/post/6844903882208837645 。
什么是柯里化
在数学和计算机科学中,柯里化是种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
举例来说,一个接收 3 个参数的普通函数,在进行柯里化后,柯里化版本的函数接收一个参数并返回接收下一个参数的函数,该函数返回一个接收第三个参数的函数。最后一个函数在接收第三个参数后, 将之前接收到的三个参数应用于原普通函数中,并返回最终结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
function sum(a, b, c) { console.log(a + b + c); }
function curry(fn) { }
let _sum = curry(sum);
let A = _sum(1); let B = A(2); B(3);
|
对于 Javascript 语言,我们通常说的柯里化函数的概念与数学和计算机科学中的柯里化的概念不完全一样。
在数学和计算机科学中的柯里化函数一次只能传递一个参数,
而 Javascript 实际应用中的柯里化函数,可以传递一个或多个参数。
1 2 3 4 5 6 7 8 9 10 11
| function fn(a, b, c, d, e) { console.log(a, b, c, d, e); }
let _fn = curry(fn);
_fn(1, 2, 3, 4, 5); _fn(1)(2)(3, 4, 5); _fn(1, 2)(3, 4)(5); _fn(1)(2)(3)(4)(5);
|
柯里化的用途
柯里化实际是把简答的问题复杂化了,但是复杂化的同时,我们在使用函数时拥有了更加多的自由度。其中对于函数参数的自由处理,正是柯里化的核心所在。柯里化本质上是降低通用性,提高适用性。
假定我们有这样一段数据:
1
| const list = [{ name: "lucy" }, { name: "jack" }];
|
我们需要获取数据中的所有 name 属性的值,常规思路下,我们会这样实现:
1
| const names = list.map((item) => item.name);
|
用柯里化的思维来实现:
1 2 3 4
| let prop = curry(function (key, obj) { return obj[key]; }); const names = list.map(prop("name"));
|
仅仅只是为了获取 name 的属性值,为何还要实现一个 prop 函数呢,这样太麻烦了吧。
我们可以换个思路,prop 函数实现一次后,以后是可以多次使用的,所以我们在考虑代码复杂程度的时候,是可以将 prop 函数的实现去掉的。我们实际的代码可以理解为只有一行:
1
| const names = list.map(prop("name"));
|
这么看来,通过柯里化的方式,我们的代码变得更精简并且可读性更高。
封装柯里化工具函数
对于柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。
当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?有两种思路:
- 通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
- 在调用柯里化工具函数时,手动指定所需的参数个数
两点结合以下,实现一个简单 curry 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
function curry(fn, len = fn.length) { return _curry.call(this, fn, len); }
function _curry(fn, len, ...args) { return function (...params) { let _args = [...args, ...params]; if (_args.length >= len) return fn.apply(this, _args); else return _curry.call(this, fn, len, ..._args); }; }
|
验证:
1 2 3 4 5 6 7 8
| let _fn = curry(function (a, b, c, d, e) { console.log(a, b, c, d, e); });
_fn(1, 2, 3, 4, 5); _fn(1)(2)(3, 4, 5); _fn(1, 2)(3, 4)(5); _fn(1)(2)(3)(4)(5);
|
而且,可以通过占位符的方式来改变传入参数的顺序。比如说,我们传入一个占位符,本次调用传递的参数略过占位符,占位符所在的位置由下次调用的参数来填充。
使用占位符的目的是改变参数传递的顺序,所以在 curry 函数实现中,每次需要记录是否使用占位符,并且记录占位符所代表的参数位置。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
function curry(fn, length = fn.length, holder = curry) { return _curry.call(this, fn, length, holder, [], []); }
function _curry(fn, length, holder, args, holders) { return function (..._args) { let params = args.slice(); let _holders = holders.slice(); _args.forEach((arg, i) => { if (arg !== holder && holders.length) { let index = holders.shift(); _holders.splice(_holders.indexOf(index), 1); params[index] = arg; } else if (arg !== holder && !holders.length) { params.push(arg); } else if (arg === holder && !holders.length) { params.push(arg); _holders.push(params.length - 1); } else if (arg === holder && holders.length) { holders.shift(); } }); if ( params.length >= length && params.slice(0, length).every((i) => i !== holder) ) { return fn.apply(this, params); } else { return _curry.call(this, fn, length, holder, params, _holders); } }; }
|
验证:
1 2 3 4 5 6 7 8 9 10 11 12 13
| let fn = function (a, b, c, d, e) { console.log([a, b, c, d, e]); };
let _ = {}; let _fn = curry(fn, 5, _);
_fn(1, 2, 3, 4, 5); _fn(_, 2, 3, 4, 5)(1); _fn(1, _, 3, 4, 5)(2); _fn(1, _, 3)(_, 4, _)(2)(5); _fn(1, _, _, 4)(_, 3)(2)(5); _fn(_, 2)(_, _, 4)(1)(3)(5);
|