当前位置: 首页 > news >正文

JavaScript函数柯里化

目录

函数柯里化

一、柯里化的核心概念

1. 基本定义

2. 核心特点

二、柯里化的实现方式

1. 手动实现

2. ES6 箭头函数简化

三、柯里化的应用场景

1. 参数复用

2. 延迟执行

3. 函数组合

四、柯里化与部分应用(Partial Application)的区别

五、柯里化的优缺点

优点:

缺点:

六、实际应用案例

1. React 高阶组件(HOC)

2. Redux 中间件

七、总结

 _.curry 详解

一、核心功能

二、源码实现分析

三、关键逻辑拆解

1. 参数合并与占位符处理

2. 参数数量检查与执行

3. 闭包与状态保存

四、边界处理与设计亮点

1. 动态 arity 支持

2. 占位符灵活性

3. 链式调用与参数累积

五、使用示例

1. 基础柯里化

2. 自定义 arity

六、总结


函数柯里化

柯里化(Currying) 是一种将接受 多个参数的函数 转换为一系列 接受单个参数的函数 的技术,其核心思想是 分步传递参数,逐步生成更具体的函数。这种技术由数学家 Haskell Curry 提出,广泛应用于函数式编程中,以提高代码的复用性和组合性。


一、柯里化的核心概念

1. 基本定义
  • 普通函数:一次传递所有参数。

    function add(a, b, c) { return a + b + c; }
    add(1, 2, 3); // 6
  • 柯里化函数:分步传递参数,每次接收一个参数并返回新函数。

    function curriedAdd(a) {
      return function(b) {
        return function(c) {
          return a + b + c;
        };
      };
    }
    curriedAdd(1)(2)(3); // 6
2. 核心特点
  • 参数复用:固定部分参数,生成更专用的函数。

  • 延迟执行:参数未全部传递时,函数不会执行。

  • 函数组合:便于组合多个函数形成新功能。


二、柯里化的实现方式

1. 手动实现

通过闭包和递归逐步收集参数:

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

// 使用示例
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
2. ES6 箭头函数简化

利用箭头函数隐式返回特性:

const curry = (fn) => 
  curried = (...args) => 
    args.length >= fn.length 
      ? fn(...args) 
      : (...nextArgs) => curried(...args, ...nextArgs);

const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6

三、柯里化的应用场景

1. 参数复用

固定部分参数,生成特定功能的函数:

// 通用日志函数
const log = (level, message) => console.log(`[${level}] ${message}`);

// 柯里化生成特定级别的日志函数
const debugLog = curry(log)('DEBUG');
debugLog('User logged in'); // [DEBUG] User logged in

const errorLog = curry(log)('ERROR');
errorLog('Database connection failed'); // [ERROR] Database connection failed
2. 延迟执行

动态组合参数,最后统一执行:

// 发送 API 请求
const sendRequest = (method, url, data) => { /* ... */ };

// 柯里化生成 POST 请求函数
const post = curry(sendRequest)('POST');
post('/api/users')({ name: 'Alice' });

// 进一步生成特定路径的 POST 函数
const postToUsers = post('/api/users');
postToUsers({ name: 'Bob' });
3. 函数组合

结合 compose 或 pipe 实现复杂逻辑:

// 组合多个函数
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

// 柯里化处理数据流
const toUpperCase = str => str.toUpperCase();
const exclaim = str => str + '!';
const emphasize = compose(curry(exclaim), curry(toUpperCase));

console.log(emphasize('hello')); // "HELLO!"

四、柯里化与部分应用(Partial Application)的区别

特性柯里化(Currying)部分应用(Partial Application)
参数传递每次只接受一个参数,逐层嵌套函数可一次传递多个参数,固定部分参数
返回结果返回链式函数,直到所有参数收集完毕直接返回接受剩余参数的函数
灵活性严格按参数顺序分解可任意选择固定参数的位置(如 _ 占位符)

示例对比

// 柯里化
const add = a => b => c => a + b + c;
add(1)(2)(3); // 6

// 部分应用
const partialAdd = (a, b) => c => a + b + c;
partialAdd(1, 2)(3); // 6

五、柯里化的优缺点

优点
  • 提高函数复用性:通过参数复用生成专用函数。

  • 增强代码可读性:链式调用更符合自然语言逻辑(如 sendRequest('GET')('/api/data'))。

  • 便于函数组合:与高阶函数(mapfilter)结合更灵活。

缺点
  • 性能开销:多次嵌套函数调用可能影响性能。

  • 代码复杂度:过度使用会降低代码可读性(如深层嵌套的箭头函数)。

  • 参数顺序限制:必须按定义顺序传递参数。


六、实际应用案例

1. React 高阶组件(HOC)

通过柯里化传递配置参数:

const withLoading = (Component) => ({ isLoading, ...props }) => 
  isLoading ? <Spinner /> : <Component {...props} />;

// 柯里化增强组件
const EnhancedTable = withLoading(Table);
<EnhancedTable isLoading={true} data={data} />
2. Redux 中间件

处理异步 Action 的柯里化中间件:

const thunkMiddleware = ({ dispatch, getState }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  return next(action);
};

七、总结

  • 柯里化本质:将多参数函数转换为单参数函数的链式调用。

  • 核心价值:参数复用、延迟执行、增强函数组合能力。

  • 适用场景:高频复用参数、动态生成函数、复杂逻辑拆解。

  • 注意事项:避免过度使用,权衡可读性与灵活性。

代码实践建议

  • 使用 Lodash 的 _.curry 或 Ramda 的 R.curry 简化柯里化过程。

  • 结合 TypeScript 增强类型提示,避免参数类型错误。


   

 _.curry 详解


一、核心功能

_.curry 将多参数函数转换为柯里化(Currying)函数,允许 分步传递参数,直到参数数量达到指定 arity(默认取原函数的形参数量 func.length)。
支持特性

  • 占位符_):允许跳过某些参数位置,后续调用填充。

  • 灵活调用:支持单参数调用(如 curried(a)(b)(c))或批量传递(如 curried(a, b, c))。


二、源码实现分析

以下为简化后的核心逻辑(基于 Lodash v4.17.21):

import placeholder from './placeholder';

function curry(func, arity = func.length) {
  // 生成柯里化包装函数
  function curried(...args) {
    // 合并当前参数与之前的参数(处理占位符)
    const combined = combineArguments(args, curried.placeholder, curried.args);

    // 检查是否满足参数数量要求
    if (combined.length >= arity) {
      return executeFunc(func, this, combined.slice(0, arity));
    } else {
      // 继续返回柯里化函数,保存已合并的参数
      return createCurried(func, arity, combined);
    }
  }

  // 初始化参数存储和占位符配置
  curried.args = [];
  curried.placeholder = placeholder;

  return curried;
}

// 辅助函数:合并新旧参数,替换占位符
function combineArguments(newArgs, placeholder, oldArgs = []) {
  const result = [...oldArgs];
  let newIndex = 0;

  // 遍历旧参数,替换占位符
  for (let i = 0; i < result.length; i++) {
    if (result[i] === placeholder && newIndex < newArgs.length) {
      result[i] = newArgs[newIndex++];
    }
  }

  // 添加剩余新参数到末尾
  while (newIndex < newArgs.length) {
    result.push(newArgs[newIndex++]);
  }

  return result;
}

// 辅助函数:创建新的柯里化函数,继承参数和配置
function createCurried(func, arity, args) {
  const newCurried = curry(func, arity);
  newCurried.args = args;
  return newCurried;
}

// 辅助函数:执行原函数,绑定 this 和参数
function executeFunc(func, thisArg, args) {
  return func.apply(thisArg, args);
}

三、关键逻辑拆解

1. 参数合并与占位符处理
  • combineArguments 函数

    • 输入:新参数 newArgs、占位符标识 placeholder、旧参数 oldArgs

    • 步骤

      1. 遍历旧参数,用新参数替换占位符(从左到右填充)。

      2. 将剩余新参数追加到结果末尾。

    • 示例

      // 旧参数: [1, _, 3]
      // 新参数: [2]
      // 合并结果: [1, 2, 3]
2. 参数数量检查与执行
  • 条件判断

    • 当已合并的参数数量 >= arity 时,调用原函数 func

    • 否则,生成新的柯里化函数(携带已合并的参数)。

3. 闭包与状态保存
  • curried 函数

    • 通过闭包保存 arity 和原函数 func

    • 通过属性 curried.args 存储已合并的参数。

    • 通过 curried.placeholder 标识占位符(如 _)。


四、边界处理与设计亮点

1. 动态 arity 支持
  • 默认值arity = func.length,但手动指定可避免 func.length 不准确的问题(如参数默认值、剩余参数)。

    // 原函数参数含默认值,func.length 可能不符合预期
    function sum(a, b = 2) {}
    console.log(sum.length); // 1
    
    // 手动指定 arity=2
    const curriedSum = _.curry(sum, 2);
2. 占位符灵活性
  • 跳过参数位置:允许后续调用填充任意位置的占位符。

    const curried = _.curry((a, b, c) => a + b + c);
    curried(1, _, 3)(2); // 6(等效于 curried(1, 2, 3))
3. 链式调用与参数累积
  • 多次调用:每次调用将参数合并到已有参数列表中,直到参数足够。

    const curriedAdd = _.curry((a, b, c) => a + b + c);
    const temp = curriedAdd(1);      // 参数: [1]
    const temp2 = temp(2);           // 参数: [1, 2]
    temp2(3);                        // 6

五、使用示例

1. 基础柯里化
const add = (a, b, c) => a + b + c;
const curriedAdd = _.curry(add);

curriedAdd(1)(2)(3);     // 6
curriedAdd(1, 2)(3);     // 6
curriedAdd(1, _, 3)(2);  // 6(使用占位符)
2. 自定义 arity
function dynamicArgs(...args) {
  return args.slice(0, 3).join('-');
}
const curried = _.curry(dynamicArgs, 3); // 手动指定 arity=3
curried('a')('b')('c');                 // 'a-b-c'

六、总结

设计要点实现细节
参数合并替换占位符,累积参数直到满足 arity
状态管理通过闭包和函数属性保存已收集参数和配置
占位符支持动态替换机制,允许灵活填充参数位置
动态 arity支持手动指定参数数量,避免依赖 func.length 的局限性
链式调用返回新柯里化函数,直至参数足够后执行原函数

源码亮点

  • 模块化辅助函数combineArgumentscreateCurried 等提升可维护性。

  • 占位符替换策略:确保参数填充顺序的灵活性。

  • 闭包状态管理:优雅地保存中间参数,避免污染外部作用域。


相关文章:

  • kubectl修改资源时添加注解
  • Vim 使用全攻略:从入门到精通
  • 蓝牙测试中 PRBS9 数据包类型
  • Docker Swarm 集群
  • 信息安全管理与评估2019年国赛正式卷以及十套国赛卷答案截图
  • 机器学习的一百个概念(12)学习率
  • VisionTransformer 有效涨点改进:添加Star_Block模块 (2024改进方法)
  • 【01】Arduino编程基础知识
  • 音视频学习(三十三):GOP详解
  • mac安装python
  • 五、adb常用命令
  • 基于web的民宿信息系统(源码+lw+部署文档+讲解),源码可白嫖!
  • 中间件--ClickHouse-2--OLAP和OLTP
  • c++:构造函数(Constructor)与析构函数(Destructor)
  • 基于 LSTM 的多特征序列预测-SHAP可视化!
  • 利用 Python 进行股票数据可视化分析
  • 做防水两步走,一步选材料一步定施工
  • 2.微服务拆分流程
  • 如果想在 bean 创建出来之前和销毁之前做一些自定义操作可以怎么来实现呢?
  • 6.2 GitHub API接口设计实战:突破限流+智能缓存实现10K+仓库同步
  • 电商网站建设目的/百度店铺注册
  • 东莞塘厦网站建设/5g站长工具查询
  • 东莞什么平台好做/广东seo网络培训
  • 班级网站怎么做/seo教程自学入门教材
  • 网站设计 推广/seo优化是利用规则提高排名
  • 产品展示网站建设/广告网络营销