函数柯里化(Currying)
函数柯里化是将多参数函数转换为一系列单参数函数的技术,是函数式编程的重要概念,常用于参数复用和函数组合。
#resource / javascript
#resource / ecmascript
#type / concept
#status / evergreen
[!info] related notes
函数柯里化(Currying)
一句话定义
柯里化(Currying)是将一个接受多个参数的函数,转换为一系列只接受单个参数的函数的技术。
核心机制
基本概念
// 原始函数
const add = (a, b, c) => a + b + c;
// 柯里化后
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6
curriedAdd(1, 2, 3); // 6
关键点
- 返回函数:
curry()返回一个新函数 - 参数累积:每次调用累积参数,直到参数数量满足原函数要求
- 自动执行:参数足够时自动执行原函数并返回结果
- 支持部分应用:可以分多次传入参数
实现方式
实现 1:使用 bind()
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.call(this, ...args);
}
// 创建新函数,预填充已传入的参数
const boundFn = fn.bind(this, ...args);
return curry(boundFn);
};
}
原理:Function.prototype.bind() 会创建一个新函数,并预填充部分参数,同时 bind() 返回的函数的 length 属性会反映剩余参数数量。
实现 2:改进版(更简洁)
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.call(this, ...args);
}
// 直接绑定 curried 函数本身
return curried.bind(this, ...args);
};
}
优势:避免创建额外的中间函数,更简洁高效。
实现 3:显式参数合并
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.call(this, ...args);
}
return function(...missingArgs) {
return curried.call(this, ...args, ...missingArgs);
};
};
}
优势:逻辑更清晰,不依赖 bind(),更容易理解。
最小例子
const join = (a, b, c) => `${a}_${b}_${c}`;
const curriedJoin = curry(join);
curriedJoin(1, 2, 3); // '1_2_3'
curriedJoin(1)(2, 3); // '1_2_3'
curriedJoin(1, 2)(3); // '1_2_3'
curriedJoin(1)(2)(3); // '1_2_3'
实际应用
1. 参数复用
// 创建一个通用的日志函数
const log = curry((level, message) => {
console.log(`[${level}] ${message}`);
});
const info = log('INFO');
const error = log('ERROR');
info('应用启动'); // [INFO] 应用启动
error('发生错误'); // [ERROR] 发生错误
2. 事件处理
const addEventListener = curry((event, handler, element) => {
element.addEventListener(event, handler);
});
const onClick = addEventListener('click');
const onHover = addEventListener('mouseover');
onClick(handleClick)(button);
onHover(handleHover)(link);
3. API 请求
const fetchFrom = curry((baseUrl, endpoint) => {
return fetch(`${baseUrl}${endpoint}`);
});
const fetchFromAPI = fetchFrom('https://api.example.com');
const fetchUsers = fetchFromAPI('/users');
const fetchPosts = fetchFromAPI('/posts');
与其他概念的关系
与偏函数应用(Partial Application)的区别
| 特性 | 柯里化(Currying) | 偏函数应用(Partial Application) |
|---|---|---|
| 参数数量 | 每次只接受一个参数 | 可以一次接受多个参数 |
| 返回值 | 总是返回函数 | 参数足够时返回结果 |
| 实现方式 | 递归或 bind | 简单的参数预填充 |
// 柯里化
const curriedAdd = curry((a, b, c) => a + b + c);
curriedAdd(1)(2)(3); // 6
// 偏函数应用
const partialAdd = (a) => (b, c) => a + b + c;
partialAdd(1)(2, 3); // 6
与闭包的关系
柯里化依赖闭包来保存已传入的参数。每次返回的函数都会捕获之前的参数,直到最终执行。
常见误解
1. 箭头函数没有 arguments 对象
// ❌ 错误:箭头函数没有 arguments
const curried = (...args) => {
console.log(arguments); // ReferenceError
};
// ✅ 正确:使用 rest 参数
const curried = (...args) => {
console.log(args); // [1, 2, 3]
};
2. this 绑定问题
const obj = {
name: 'test',
greet: curry(function(greeting) {
return `${greeting}, ${this.name}`;
})
};
// ❌ 可能丢失 this
const greet = obj.greet;
greet('Hello'); // this 可能不是 obj
// ✅ 正确:保持 this 绑定
obj.greet('Hello')('World');
3. 函数 length 属性
Function.prototype.length 返回函数定义时的形参数量,不包括默认参数和剩余参数。
function fn(a, b, c) {}
console.log(fn.length); // 3
function fn2(a, b, c = 1) {}
console.log(fn2.length); // 2
function fn3(...args) {}
console.log(fn3.length); // 0
BFE.dev 相关题目
- 1. implement curry():实现柯里化函数
- 19. this:理解 this 绑定
- 33. this II:this 绑定进阶
- 41. this III:this 绑定综合
- 49. this IV:this 绑定高级
- 97. this V:this 绑定面试题
面试要点
- 实现柯里化函数:能够手写
curry()函数 - 理解参数累积:知道如何累积参数直到满足原函数要求
- this 绑定:理解如何在柯里化中保持正确的 this 绑定
- 函数 length 属性:知道如何获取函数的参数数量
- 实际应用场景:能够举例说明柯里化的实际应用