什么是柯里化函数?JavaScript 中有哪些使用场景?
什么是柯里化?
柯里化(Currying)是将一个接受多个参数的函数,转换成一系列每次只接受一个参数的函数。转换后的函数会逐步收集参数,直到参数齐全才执行原函数。
javascript// 普通函数 function add(a, b, c) { return a + b + c; } add(1, 2, 3); // 6 // 柯里化后 function curryAdd(a) { return function(b) { return function(c) { return a + b + c; }; }; } curryAdd(1)(2)(3); // 6
核心价值是参数复用和延迟执行——你可以先固定部分参数,得到一个更具体的函数,等剩余参数就位再调用。
手写通用 curry 函数
面试中最常考的是手写一个通用的 curry,它能将任意多参数函数转为柯里化形式:
javascriptfunction curry(fn) { return function curried(...args) { // 收集的参数数量 >= 原函数参数数量,直接执行 if (args.length >= fn.length) { return fn.apply(this, args); } // 否则返回新函数继续收集 return (...nextArgs) => curried.apply(this, args.concat(nextArgs)); }; }
验证:
javascriptfunction sum(a, b, c) { return a + b + c; } const curriedSum = curry(sum); curriedSum(1)(2)(3); // 6 curriedSum(1, 2)(3); // 6 —— 支持一次传多个参数 curriedSum(1)(2, 3); // 6
关键点:利用 fn.length 判断原函数期望的参数个数,利用闭包在调用链中持续传递已收集的参数。
柯里化的实际使用场景
参数复用:创建预设函数
javascriptfunction log(level, timestamp, message) { console.log(`[${level}] ${timestamp}: ${message}`); } const curriedLog = curry(log); const errorLog = curriedLog('ERROR'); // 预设 level const errorLogNow = errorLog(Date.now()); // 预设 timestamp errorLogNow('接口超时'); // [ERROR] 1748400000000: 接口超时 errorLogNow('数据解析失败'); // 复用前两个参数
bind 方法的本质
Function.prototype.bind 就是偏函数应用——预设 this 和部分参数,返回新函数:
javascriptconst obj = { x: 42 }; function getX(extra) { return this.x + extra; } const boundGetX = getX.bind(obj, 8); // 预设 this + 第一个参数 boundGetX(); // 50
Redux 中间件的函数链
Redux 中间件签名 store => next => action => {} 就是三级柯里化:
javascriptconst logger = store => next => action => { console.log('dispatching', action); const result = next(action); console.log('next state', store.getState()); return result; };
第一层拿到 store,第二层拿到 next(下一个中间件),第三层处理 action。分层柯里化使得每层的职责清晰,也方便测试——你可以只传入部分参数来测试中间件的某一步。
React 事件处理中的参数绑定
jsx// 方式一:bind 预设参数 <button onClick={handleClick.bind(null, item.id)}>删除</button> // 方式二:高阶函数(本质也是柯里化) const handleClick = (id) => (e) => { e.stopPropagation(); deleteItem(id); }; <button onClick={handleClick(item.id)}>删除</button>
柯里化与偏函数的区别
两者容易混淆,核心区别在于参数接收方式:
- 柯里化:将函数转为一系列一元函数,每次只接受一个参数(
f(a)(b)(c)) - 偏函数:预先填充部分参数,返回的函数仍可接受多个参数(
f(a, b)→f'(c, d))
javascript// 柯里化:每次一个参数 const curriedAdd = curry((a, b, c) => a + b + c); curriedAdd(1)(2)(3); // 6 // 偏函数:一次传剩余参数 function partial(fn, ...presetArgs) { return (...laterArgs) => fn(...presetArgs, ...laterArgs); } const addFive = partial((a, b) => a + b, 5); addFive(3); // 8
bind 是偏函数,不是严格柯里化——因为 bind 返回的函数可以一次接收多个参数。
柯里化的局限与注意事项
性能开销:每次柯里化调用都会创建新的闭包和函数对象,在性能敏感场景(如高频事件处理、大循环内)应避免过度使用。
只处理显式参数:fn.length 无法识别默认参数、rest 参数。带默认值的函数柯里化后行为可能不符合预期:
javascriptfunction greet(name, greeting = 'Hello') { return `${greeting}, ${name}`; } greet.length; // 1,只有 name 是显式参数 curry(greet)('Alice'); // 直接返回 "Hello, Alice",跳过了 greeting 的柯里化步骤
调试困难:柯里化链越长,调用栈越深,错误堆栈的可读性越差。生产代码中应控制嵌套层级。
追问
柯里化和偏函数有什么区别?
柯里化将函数拆成一元函数链(f(a)(b)(c)),偏函数预设部分参数但返回函数仍可多参(f(a, b) → f'(c, d))。bind 是偏函数不是严格柯里化。
能手写一个通用的 curry 函数吗?
利用闭包持续收集参数,用 fn.length 判断参数是否齐全,齐全则执行,否则返回新函数继续收集。见上方"手写通用 curry 函数"部分。
柯里化在 React 中有什么应用?
事件处理器的参数预绑定:onClick={handleClick(id)} 用高阶函数延迟执行;Hooks 中的自定义 Hook 参数分步传入也是类似思路。