实现一个JS函数柯里化
手写实现函数柯里化,将多参数函数转换为单参数函数的嵌套调用形式
问题
函数柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。简单来说,就是将 f(a, b, c) 转换为 f(a)(b)(c) 的形式。
需要实现一个通用的柯里化函数,能够将任意多参数函数转换为柯里化形式。
解答
/**
* 函数柯里化实现
* @param {Function} fn - 需要柯里化的函数
* @param {Array} args - 已经收集的参数
* @returns {Function} 柯里化后的函数
*/
function curry(fn, ...args) {
// 获取函数的参数个数
const length = fn.length;
return function(...newArgs) {
// 合并已收集的参数和新传入的参数
const allArgs = [...args, ...newArgs];
// 如果参数个数足够,则执行原函数
if (allArgs.length >= length) {
return fn.apply(this, allArgs);
} else {
// 否则继续返回柯里化函数,收集参数
return curry.call(this, fn, ...allArgs);
}
};
}
// 方法二:更简洁的实现
function curry2(fn) {
return function curried(...args) {
// 参数够了就执行,不够就继续返回函数
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...newArgs) {
return curried.apply(this, [...args, ...newArgs]);
};
}
};
}
// 方法三:支持占位符的柯里化
function curryWithPlaceholder(fn, placeholder = '_') {
return function curried(...args) {
// 检查是否有占位符,以及参数是否足够
const hasPlaceholder = args.some(arg => arg === placeholder);
if (!hasPlaceholder && args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...newArgs) {
// 替换占位符
const finalArgs = args.map(arg =>
arg === placeholder && newArgs.length ? newArgs.shift() : arg
);
// 添加剩余的新参数
return curried.apply(this, [...finalArgs, ...newArgs]);
};
};
}
使用示例
// 示例1:基础使用
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
// 示例2:实际应用场景 - 日志函数
function log(level, time, message) {
console.log(`[${level}] ${time}: ${message}`);
}
const curriedLog = curry(log);
const errorLog = curriedLog('ERROR');
const errorLogNow = errorLog(new Date().toISOString());
errorLogNow('用户登录失败'); // [ERROR] 2024-01-01T00:00:00.000Z: 用户登录失败
errorLogNow('数据库连接失败'); // [ERROR] 2024-01-01T00:00:00.000Z: 数据库连接失败
// 示例3:数学计算
function multiply(a, b, c) {
return a * b * c;
}
const curriedMultiply = curry(multiply);
const double = curriedMultiply(2);
const doubleThenTriple = double(3);
console.log(doubleThenTriple(4)); // 24
// 示例4:使用占位符
const curriedAddWithPlaceholder = curryWithPlaceholder(add);
const add5 = curriedAddWithPlaceholder('_', '_', 5);
console.log(add5(1, 2)); // 8
console.log(add5(3, 4)); // 12
关键点
-
参数收集:通过闭包保存已经传入的参数,每次调用时将新参数与旧参数合并
-
参数判断:使用
fn.length获取原函数的参数个数,当收集的参数数量达到要求时执行原函数 -
递归调用:参数不足时返回新的柯里化函数,继续收集参数,形成链式调用
-
this 绑定:使用
apply或call确保函数执行时 this 指向正确 -
灵活调用:支持一次传入多个参数,如
f(1,2)(3)和f(1)(2)(3)都能正常工作 -
扩展功能:可以实现占位符功能,允许跳过某些参数位置,提供更灵活的参数传递方式
-
应用场景:参数复用、延迟执行、动态生成函数等场景,特别适合函数式编程风格
目录