5月28日 03:23

什么是柯里化函数?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,它能将任意多参数函数转为柯里化形式:

javascript
function curry(fn) { return function curried(...args) { // 收集的参数数量 >= 原函数参数数量,直接执行 if (args.length >= fn.length) { return fn.apply(this, args); } // 否则返回新函数继续收集 return (...nextArgs) => curried.apply(this, args.concat(nextArgs)); }; }

验证:

javascript
function 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 判断原函数期望的参数个数,利用闭包在调用链中持续传递已收集的参数。

柯里化的实际使用场景

参数复用:创建预设函数

javascript
function 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 和部分参数,返回新函数:

javascript
const obj = { x: 42 }; function getX(extra) { return this.x + extra; } const boundGetX = getX.bind(obj, 8); // 预设 this + 第一个参数 boundGetX(); // 50

Redux 中间件的函数链

Redux 中间件签名 store => next => action => {} 就是三级柯里化:

javascript
const 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 参数。带默认值的函数柯里化后行为可能不符合预期:

javascript
function 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 参数分步传入也是类似思路。

标签:JavaScript前端