JavaScript中的柯里化(Currying)与偏函数(Partial Application)
字数 514 2025-11-10 11:36:08
JavaScript中的柯里化(Currying)与偏函数(Partial Application)
描述
柯里化和偏函数是函数式编程中的两个重要概念,用于将多参数函数转换为更小单元的函数。虽然它们都涉及函数转换,但有着不同的应用场景和实现方式。柯里化是将一个接受多个参数的函数转换为一系列使用单个参数的函数,而偏函数是预先填充原函数的部分参数,生成一个参数更少的新函数。
详细讲解
1. 基本概念区分
- 柯里化:将f(a, b, c)转换为f(a)(b)(c)的调用形式
- 偏函数:将f(a, b, c)转换为f_partial(a, b)的形式(提前固定部分参数)
2. 手动实现柯里化
// 原始函数
function add(a, b, c) {
return a + b + c;
}
// 手动柯里化版本
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
}
}
}
// 使用对比
add(1, 2, 3); // 6
curriedAdd(1)(2)(3); // 6
3. 通用柯里化函数的实现
function curry(fn) {
return function curried(...args) {
// 如果参数数量足够,直接执行原函数
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// 参数不足时,返回新函数继续收集参数
else {
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
}
}
}
}
// 应用示例
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24
4. 偏函数的实现与应用
// 简单偏函数实现
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn.apply(this, presetArgs.concat(laterArgs));
}
}
// 应用示例
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`;
}
const sayHello = partial(greet, 'Hello');
const sayHi = partial(greet, 'Hi');
console.log(sayHello('Alice', '!')); // "Hello, Alice!"
console.log(sayHi('Bob', '!!!')); // "Hi, Bob!!!"
5. 实际应用场景
- 参数复用:创建可复用的基础函数
// 柯里化应用:创建特定基数的对数函数
const log = base => x => Math.log(x) / Math.log(base);
const log2 = log(2);
const log10 = log(10);
console.log(log2(8)); // 3
console.log(log10(1000)); // 3
- 延迟执行:参数不足时函数不会立即执行
// 偏函数应用:创建通用的数据验证器
function validate(pattern, message, value) {
return pattern.test(value) ? null : message;
}
const validateEmail = partial(validate, /^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'Invalid email');
const validatePhone = partial(validate, /^\d{10}$/, 'Invalid phone');
console.log(validateEmail('test@example.com')); // null
console.log(validatePhone('123')); // "Invalid phone"
6. 高级技巧:占位符支持
// 支持占位符的偏函数
function partialWithPlaceholder(fn, ...presetArgs) {
return function(...laterArgs) {
let finalArgs = [];
let laterIndex = 0;
for (const arg of presetArgs) {
if (arg === partialWithPlaceholder.placeholder) {
finalArgs.push(laterArgs[laterIndex++]);
} else {
finalArgs.push(arg);
}
}
// 添加剩余的参数
finalArgs = finalArgs.concat(laterArgs.slice(laterIndex));
return fn.apply(this, finalArgs);
}
}
partialWithPlaceholder.placeholder = Symbol('placeholder');
// 使用占位符
function concat3(a, b, c) {
return a + b + c;
}
const _ = partialWithPlaceholder.placeholder;
const concatPrefixSuffix = partialWithPlaceholder(concat3, 'pre-', _, '-suf');
console.log(concatPrefixSuffix('middle')); // "pre-middle-suf"
7. 注意事项
- 柯里化要求函数参数数量固定(无默认参数或剩余参数)
- 偏函数更适合处理参数数量可变的情况
- 在性能敏感场景需谨慎使用,因为闭包创建会有额外开销
通过理解柯里化和偏函数的区别与联系,你可以编写出更加灵活和可复用的函数,提升代码的函数式编程特性。