柯里化(Currying)

一句话理解

柯里化就是把 f(a, b, c) 变成 f(a)(b)(c)

把一个接收多个参数的函数,拆成一系列只接收一个参数的函数链。

为什么需要它?

假设你有一个日志函数:

function log(level, message) {
  console.log(`[${level}] ${message}`);
}

log('INFO', '系统启动');
log('INFO', '用户登录');
log('INFO', '数据加载完成');

每次都要重复写 'INFO',很烦。柯里化可以解决:

const log = level => message => console.log(`[${level}] ${message}`);

const info = log('INFO');    // 先固定 level
const error = log('ERROR');

info('系统启动');    // [INFO] 系统启动
info('用户登录');    // [INFO] 用户登录
error('连接失败');   // [ERROR] 连接失败

效果:把公共参数提前固定,后面只用关注变化的参数。

基础实现

手动写一个简单的柯里化函数

function curry(fn) {
  return function curried(...args) {
    // 如果参数够了,直接执行原函数
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    // 否则继续收集参数
    return (...nextArgs) => curried(...args, ...nextArgs);
  };
}

// 使用
function add(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(add);
curriedAdd(1)(2)(3);  // 6
curriedAdd(1, 2)(3);  // 6(也支持一次传多个)

用箭头函数手动柯里化(最简单直观)

// 三步走
const add = a => b => c => a + b + c;

add(1)(2)(3);  // 6

常见应用场景

1. 配置函数

// 网络请求
const request = baseURL => endpoint => params =>
  fetch(`${baseURL}${endpoint}`, params);

const api = request('https://api.softool.cn');
const getUser = api('/user');
const getPosts = api('/posts');

getUser({ id: 1 });    // 请求 https://api.softool.cn/user?id=1
getPosts({ page: 1 }); // 请求 https://api.softool.cn/posts?page=1

2. 事件处理

const handleClick = action => event => {
  console.log(`执行操作: ${action}`);
  // 处理点击...
};

const saveClick = handleClick('保存');
const deleteClick = handleClick('删除');

button.addEventListener('click', saveClick);
deleteBtn.addEventListener('click', deleteClick);

3. 数据转换管道

const map = fn => arr => arr.map(fn);
const filter = fn => arr => arr.filter(fn);
const reduce = fn => init => arr => arr.reduce(fn, init);

const double = map(x => x * 2);
const even = filter(x => x % 2 === 0);

double([1, 2, 3]);       // [2, 4, 6]
even([1, 2, 3, 4]);      // [2, 4]

和普通函数的区别

普通函数 柯里化函数
add(1, 2, 3) add(1)(2)(3)
一次性给完所有参数 分步给参数
不能中间暂停 每步都可以复用中间结果
适合简单调用 适合参数固定场景

注意事项

  1. 不要滥用:不是所有函数都需要柯里化,只有当你需要复用部分参数时才值得用
  2. 性能:多层嵌套会有微小开销,但对日常开发影响可忽略
  3. 可读性:过度柯里化会让代码难懂,适度使用

一句话记住

柯里化 = 把一次性点餐,改成自助餐分次取菜
先选主食(固定参数),再选配菜(变化参数),最后吃(执行)。

入门阶段,掌握上面这些就够了。等你熟悉后,可以去了解:

  • 自动柯里化(lodash 的 _.curry
  • 占位符(跳过某个参数)
  • 与偏应用的区别(前面聊过)