函数柯里化(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

关键点

  1. 返回函数curry() 返回一个新函数
  2. 参数累积:每次调用累积参数,直到参数数量满足原函数要求
  3. 自动执行:参数足够时自动执行原函数并返回结果
  4. 支持部分应用:可以分多次传入参数

实现方式

实现 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 绑定面试题

面试要点

  1. 实现柯里化函数:能够手写 curry() 函数
  2. 理解参数累积:知道如何累积参数直到满足原函数要求
  3. this 绑定:理解如何在柯里化中保持正确的 this 绑定
  4. 函数 length 属性:知道如何获取函数的参数数量
  5. 实际应用场景:能够举例说明柯里化的实际应用

信息参考

创建于 2026/3/31 更新于 2026/5/27