高阶函数和函数柯里化

三葉Leaves Author

听起来挺高大上的两个词,其实这个概念开发中很常用,这里就直接借助 ds 总结了。

1. 高阶函数 (Higher-Order Function, HOF)

  • ​核心定义:​
    • 一个函数如果满足以下条件​​之一​​,就是高阶函数:
      1. ​接受一个或多个函数作为参数。​
      2. ​返回一个新的函数作为其结果。​
  • ​核心思想:​​ 在JavaScript中,函数被视为“一等公民”(First-Class Citizen)。这意味着函数和其他数据类型(如数字、字符串、对象)地位平等,可以:
    • 被赋值给变量
    • 作为参数传递给其他函数
    • 作为其他函数的返回值
    • 存储在数据结构(如数组、对象)中
  • ​为什么重要?​
    • ​提升抽象层次:​​ 高阶函数允许你操作的是行为(函数)本身,而不是具体的值。代码更通用、更声明式(描述“做什么”,而不是“怎么做”)。
    • ​代码复用:​​ 通用逻辑(如遍历、转换、过滤)可以封装在高阶函数里,通过传入不同的函数参数来定制具体行为。
    • ​促进组合:​​ 高阶函数是函数组合的基础,可以将小功能单元组合成更复杂的操作。
  • ​前端开发常见示例:​
    • ​数组方法:​​ 这是最常见、最实用的高阶函数应用!

      • Array.prototype.map(func):接受一个函数func,该函数会作用到数组的每个元素上,并返回一个新数组(映射结果)。
      • Array.prototype.filter(func):接受一个函数func(常称为断言函数),该函数返回truefalse,用于筛选数组元素。
      • Array.prototype.reduce(func, initialValue?):接受一个函数func和一个可选的初始值,用于将数组“折叠”(累积)成单个值。
      • Array.prototype.forEach(func):接受一个函数func,对数组每个元素执行该操作(没有返回值)。
      • Array.prototype.find(func), Array.prototype.some(func), Array.prototype.every(func):都接受断言函数。
    • ​事件处理:​

      1
      2
      3
      4
      5
      const button = document.getElementById('myButton');
      button.addEventListener('click', function(eventHandler) {
      // 这个 eventHandler 函数就是被 addEventListener 这个HOF接受作为参数的
      console.log('Button clicked!');
      });
    • ​定时器:​

      1
      2
      3
      setTimeout(function(callback) {
      console.log('This runs after 1 second');
      }, 1000); // setTimeout 接受一个函数作为第一个参数
    • ​请求处理(回调/Promise):​

      1
      2
      3
      4
      5
      6
      7
      fetch('https://api.example.com/data')
      .then(function(response) { // .then() 接受一个处理 response 的函数
      return response.json();
      })
      .then(function(data) { // .then() 接受一个处理 data 的函数
      console.log(data);
      });
    • ​定制化行为:​

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function makeGreeter(greetingFunction) { // HOF: 接受函数作为参数
      return function(name) { // 同时也返回一个新函数!(双重HOF)
      return greetingFunction(name);
      };
      }

      const englishGreet = makeGreeter(name => `Hello, ${name}!`);
      const frenchGreet = makeGreeter(name => `Bonjour, ${name}!`);

      console.log(englishGreet('Alice')); // 输出: Hello, Alice!
      console.log(frenchGreet('Bob')); // 输出: Bonjour, Bob!

2. 函数柯里化 (Function Currying)

  • ​核心定义:​

    • 一种将接受​​多个参数的函数​​,转换成一系列接受​​单个参数​​(或更少参数)的函数的技术。
    • 柯里化后的函数不会立即求值,而是在接收到足够的参数后才执行原始函数。
  • ​核心思想:​

    • 把一个多参数函数 f(a, b, c),转化成一个嵌套的函数调用链:f(a)(b)(c)
    • 每一步转换(返回的函数)都​​记住​​了之前传递的参数。
    • 目标:​​延迟执行​​、​​部分应用(Partial Application)​​、​​创建更具体的函数​​。
  • ​手动柯里化示例:​

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 原始函数:接受3个参数
    function sum(a, b, c) {
    return a + b + c;
    }

    // 柯里化版本:
    function curriedSum(a) {
    return function(b) {
    return function(c) {
    return a + b + c;
    };
    };
    }

    const addFive = curriedSum(5); // 返回一个函数:function(b) { return function(c) { ... } }
    const addFiveAndTen = addFive(10); // 返回一个函数:function(c) { return 5 + 10 + c; }
    const result = addFiveAndTen(3); // 18

    // 或者链式调用:
    const result2 = curriedSum(5)(10)(3); // 18
  • ​部分应用 vs. 柯里化:​

    • ​部分应用(Partial Application):​​ 固定一个多参数函数的部分参数,生成一个需要更少参数的新函数。_.partial, Function.prototype.bind() 是实现部分应用的常见方式。

      1
      2
      3
      // 使用 bind 进行部分应用(固定第一个参数)
      const addTen = sum.bind(null, 10);
      const result = addTen(5, 3); // 等同于 sum(10, 5, 3) -> 18 (这里部分应用后还是多参数)
    • ​柯里化:​​ 要求每个函数只接收一个参数,总是返回一个只接受下一个参数的函数,直到所有参数被收集完毕。它是实现部分应用的一种强大且标准化的方式(每个部分应用的步骤都标准化为接受单个参数)。

  • ​为什么要用柯里化?​

    • ​参数复用:​​ 可以预先固定某些参数(如配置、环境),创建出更具体的函数,如上面例子中的 addFiveaddFiveAndTen
    • ​延迟执行:​​ 直到所有参数都准备好才执行原始操作。
    • ​函数组合:​​ 柯里化让函数组合变得更加容易和灵活。想象一下管道操作 (compose(f, g, h)(x)) 或使用 pipe(funcA, funcB, funcC) 库时,柯里化使得每个处理阶段的函数都自然可以接收上一个阶段的输出作为唯一参数。
    • ​动态创建函数:​​ 基于运行时条件生成不同的处理函数。
  • ​前端开发常见场景:​

    • ​配置预设:​

      1
      2
      3
      4
      5
      6
      7
      8
      9
      const createRequest = baseUrl => endpoint => params =>
      fetch(`${baseUrl}${endpoint}?${new URLSearchParams(params)}`);

      const apiRequest = createRequest('https://api.example.com/v1');
      const getUser = apiRequest('/users');
      const getPost = apiRequest('/posts');

      getUser({ id: 123 }); // GET https://api.example.com/v1/users?id=123
      getPost({ id: 456 }); // GET https://api.example.com/v1/posts?id=456
    • ​事件处理绑定特定上下文/参数(常配合闭包):​

      1
      2
      3
      4
      5
      6
      7
      8
      9
      const handleButtonClick = buttonId => event => {
      console.log(`Button ${buttonId} clicked!`, event.target);
      }

      const buttons = document.querySelectorAll('button');
      buttons.forEach(btn => {
      btn.addEventListener('click', handleButtonClick(btn.id));
      });
      // 每个按钮的点击处理函数都通过柯里化“记住”了自己的 id
    • ​基于框架的库设计:​​ React 高阶组件(HOC)、Redux 中间件(如 applyMiddleware(...middleware) 本身是一个HOF,它内部的操作常常涉及函数的柯里化和组合)、RxJS 操作符、Lodash/ Ramda(函数式编程工具库)中大量使用柯里化来创建灵活的工具函数。

总结与关系

  • ​高阶函数(HOF):​​ 一个更宽泛的概念,描述函数的“高阶”能力(操作其他函数)。
  • ​函数柯里化:​​ 一个具体的技术,用于转换函数的参数接收方式。​​柯里化本身经常通过高阶函数来实现(因为转换过程需要返回新函数)。​
  • ​联系:​​ 柯里化函数本身就是一个高阶函数(它返回函数)。接收柯里化函数的函数也通常是高阶函数(它接受函数作为参数)。高阶函数是函数柯里化赖以生存的环境。

掌握这两者能让你写出更抽象、更灵活、更易复用的JavaScript代码,是提升前端开发技能的关键环节。理解它们在数组方法、异步处理、事件管理和库设计中的应用尤其重要。

  • 标题: 高阶函数和函数柯里化
  • 作者: 三葉Leaves
  • 创建于 : 2025-08-07 00:00:00
  • 更新于 : 2025-08-15 11:25:39
  • 链接: https://blog.oksanye.com/7b4c81a801f4/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论