函数柯里化(Currying)是一种将多参数函数转换为一系列单参数函数的技术。它由数学家Haskell Curry命名,是函数式编程中的重要概念。
柯里化的核心思想是:一个接收多个参数的函数可以转换为接收单一参数的函数序列。例如,f(a, b, c)
可以转换为f(a)(b)(c)
。
基本柯里化实现
让我们从一个简单的加法函数开始,看看如何手动实现柯里化:
javascript
// 原始函数
function add(a, b) {
return a + b;
}
// 柯里化版本
function curriedAdd(a) {
return function(b) {
return a + b;
};
}
// 使用方式
const add5 = curriedAdd(5);
console.log(add5(3)); // 输出8
通用柯里化函数
上面的例子是针对特定函数的柯里化。我们可以编写一个通用的柯里化函数,能够处理任意参数的函数:
javascript
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
// 使用示例
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6
柯里化的作用域特性
在柯里化过程中,作用域链的保持非常重要。每次返回的新函数都能访问外层函数的参数,这是通过闭包实现的:
javascript
function multiply(a) {
return function(b) {
return function(c) {
// 内部函数可以访问所有外层函数的参数
return a * b * c;
};
};
}
const double = multiply(2);
const triple = double(1.5);
console.log(triple(10)); // 30 (2 * 1.5 * 10)
高级柯里化技巧
1. 占位符支持
我们可以实现支持占位符的柯里化,允许在某些位置延迟提供参数:
javascript
function curryWithPlaceholder(fn) {
return function curried(...args) {
// 过滤掉占位符
const actualArgs = args.slice(0, fn.length);
const hasPlaceholder = args.some(arg => arg === curryWithPlaceholder._);
if (actualArgs.length >= fn.length && !hasPlaceholder) {
return fn.apply(this, actualArgs);
} else {
return function(...args2) {
// 合并参数,用新参数替换占位符
const mergedArgs = args.map(arg =>
arg === curryWithPlaceholder._ && args2.length ? args2.shift() : arg
).concat(args2);
return curried.apply(this, mergedArgs);
};
}
};
}
curryWithPlaceholder._ = {}; // 占位符定义
// 使用示例
const curriedSum2 = curryWithPlaceholder(sum);
console.log(curriedSum2(1, curryWithPlaceholder._, 3)(2)); // 6
2. 无限参数柯里化
前面的例子都要求参数数量固定。对于可变参数函数,我们可以这样实现:
javascript
function infiniteCurry(fn) {
return function curried(...args) {
return function(...args2) {
if (args2.length === 0) {
return args.reduce((acc, val) => fn(acc, val));
}
return curried(...args, ...args2);
};
};
}
// 使用示例
const add = infiniteCurry((a, b) => a + b);
console.log(add(1)(2)(3)(4)()); // 10
柯里化的实际应用
- 参数复用:固定某些参数,生成更专用的函数
- 延迟执行:直到所有参数都收集完毕才执行
- 函数组合:便于创建管道式操作
javascript
// 参数复用示例
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 28 }
];
// 柯里化比较函数
const propEq = key => value => obj => obj[key] === value;
// 查找名为Alice的用户
const findAlice = users.find(propEq('name')('Alice'));
console.log(findAlice); // { name: 'Alice', age: 25 }
总结
函数柯里化是JavaScript中强大的技术,它通过闭包和作用域链实现了参数的逐步传递和函数的延迟执行。手动实现柯里化不仅有助于理解函数式编程的核心概念,还能在实际开发中创建更灵活、可复用的代码结构。掌握柯里化技巧可以显著提升你的JavaScript编程能力。