JavaScript 函数柯里化(Curry)原理、应用与源码实现
函数柯里化是 JavaScript 中一项重要的高阶函数技术,它将接收多个参数的函数转化为一系列只接收单个参数(或少数几个参数)的嵌套函数调用。本文详细拆解柯里化的工作原理,通过“参数复用”解决重复定义正则校验的痛点,并附带包含占位符功能的核心实现源码。
一、什么是柯里化
在计算机科学中,柯里化(Currying)是一种将使用多个参数的函数转换成一系列使用一个参数的函数的技术。其核心在于:一次传递一部分参数,返回一个函数接收剩余参数,当所有必需参数集齐后,执行原函数并返回结果。
对于 JavaScript 而言,虽然数学定义的柯里化严格限制每次只能传入一个参数,但在实际开发中,我们通常指的柯里化允许每次传入一个或多个参数,只要参数总数未达到原函数形参要求,就继续返回函数等待下一次调用。
经典示例对比:
// 普通求和函数
function sum(a, b, c) {
console.log(a + b + c);
}
// 假设 curry 是将普通函数转为柯里化版本的工具
let _sum = curry(sum);
_sum(1)(2)(3); // 依次传参
_sum(1, 2)(3); // 部分传参
_sum(1, 2, 3); // 一次性传完,直接执行
当接收到的参数数量等于原函数的形参数量时,触发执行;若不足,则返回新函数继续累积参数。
二、柯里化的核心用途:参数复用
柯里化的本质是降低通用性,提高特定场景下的适用性。它最大的价值在于参数复用和代码简化。
应用场景 1:正则表达式校验
在处理表单校验时,我们经常需要对电话号码、邮箱等进行验证。如果使用普通函数,每次校验都需要重复传入正则表达式:
function checkByRegExp(regExp, string) {
return regExp.test(string);
}
// 每次都要重新写一遍复杂的正则,冗余且易错
checkByRegExp(/^1\d{10}$/, '18600000000');
checkByRegExp(/^1\d{10}$/, '13900000000');
借助柯里化,我们可以将正则表达式作为第一层参数固定下来,生成专用的校验函数:
// 柯里化后的校验器
let _check = curry(checkByRegExp);
// 复用正则,生成专用函数
let checkPhone = _check(/^1\d{10}$/);
let checkEmail = _check(/^[\w.]+@[\w.-]+\.[a-z]{2,3}$/);
// 后续调用极其简洁,语义清晰
checkPhone('18600000000');
checkEmail('user@example.com');
这样既减少了重复代码,又提升了业务逻辑的可读性。
应用场景 2:数组数据映射
在提取对象属性时,柯里化可以让 map 操作更加精简:
let list = [{ name: 'lucy' }, { name: 'jack' }];
// 传统写法
let names = list.map(function(item) {
return item.name;
});
// 柯里化写法
let prop = curry(function(key, obj) {
return obj[key];
});
let names = list.map(prop('name'));
这种写法在配合其他高阶函数时,能显著减少匿名函数的样板代码。
三、如何封装柯里化工具函数
理解原理后,我们来动手实现一个通用的 curry 函数。实现思路基于闭包保存已接收的参数,并通过判断参数长度决定是否执行原函数。
1. 基础版实现
利用函数的 length 属性获取预期参数个数,通过递归嵌套函数来收集参数:
function curry(fn, len = fn.length) {
return _curry.call(this, fn, len);
}
function _curry(fn, len, ...args) {
return function (...params) {
let allArgs = [...args, ...params];
// 如果参数够了,执行原函数
if (allArgs.length >= len) {
return fn.apply(this, allArgs);
} else {
// 否则继续返回函数收集参数
return _curry.call(this, fn, len, ...allArgs);
}
};
}
2. 进阶版:支持占位符(Placeholder)
像 Lodash 这样的库提供了更强大的功能,允许使用占位符跳过中间参数,改变参数顺序。实现时需记录哪些位置是占位符,并在后续调用中进行替换。
以下是支持自定义占位符的实现逻辑:
/**
* @param fn 待柯里化函数
* @param length 期望参数个数
* @param holder 占位符标识
*/
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 newHolders = holders.slice();
_args.forEach((arg, i) => {
if (arg !== holder && holders.length) {
// 真实参数填补之前的占位符
let index = holders.shift();
params[index] = arg;
} else if (arg === holder) {
// 新的占位符,记录位置
if (!holders.length) {
params.push(arg);
newHolders.push(params.length - 1);
} else {
holders.shift();
}
} else {
// 普通的追加参数
params.push(arg);
}
});
// 检查是否所有前置位置都填满了非占位符参数
const isReady = params.length >= length &&
params.slice(0, length).every(p => p !== holder);
if (isReady) {
return fn.apply(this, params);
} else {
return _curry.call(this, fn, length, holder, params, newHolders);
}
};
}
// 使用示例
let _ = {};
let add = curry((a, b, c, d) => a + b + c + d, 4, _);
add(_, 2, _, 4)(1)(3); // 结果为 10
以上便是关于 JavaScript 函数柯里化的核心原理、应用场景及源码实现的详细内容,更多关于 JavaScript 高阶函数优化技巧的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









