人人都会AI编程

9.3 高阶函数:函数作为参数、函数作为返回值

更新时间:2026-07-11

在 1.2.4 节中我们提到,JavaScript 中函数是一等公民,可以像普通数据一样被传递和操作。高阶函数(Higher-Order Function)正是这一特性的直接体现——一个函数如果接受另一个函数作为参数,或者返回一个函数作为结果,那么它就是一个高阶函数

这并非某种复杂的设计模式,而是日常开发中随处可见的写法。理解高阶函数,能让你的代码更简洁、更灵活、复用性更高。

9.3.1 函数作为参数:让行为可配置

把函数作为参数传入另一个函数,本质上是在说:“这段逻辑的大框架是固定的,但其中某个步骤的具体做法由你来定。”最常见的例子就是数组方法:

const numbers = [1, 2, 3, 4, 5];

// 筛选——保留哪些元素?由传入的函数决定
const evens = numbers.filter(n => n % 2 === 0);   // [2, 4]

// 转换——如何转换每个元素?由传入的函数决定
const doubled = numbers.map(n => n * 2);           // [2, 4, 6, 8, 10]

// 排序——如何比较两个元素?由传入的函数决定
const sorted = numbers.sort((a, b) => b - a);      // [5, 4, 3, 2, 1]

filtermapsort 这些方法都不关心具体的业务规则,它们只负责遍历数组和维护结果,真正的决策逻辑被“外包”给传入的函数。这种将 不变的部分(遍历、累加)变化的部分(判断逻辑) 分离的写法,是高阶函数最普遍的应用场景。

同样的思路也广泛用于异步编程中,比如事件监听和网络请求的回调:

// 当按钮被点击时,执行什么样的逻辑?
button.addEventListener('click', () => {
  console.log('按钮被点击了');
});

// 数据请求成功后,如何处理返回的数据?
fetch('/api/user')
  .then(response => response.json())
  .then(data => {
    renderUserInfo(data);
  });

9.3.2 函数作为返回值:让函数“生产”函数

高阶函数还可以返回一个新的函数,通常是为了创建拥有特定行为的专用函数,或者为函数预设一些参数。这种写法的精髓在于“延迟执行”——现在先不计算结果,而是返回一个随时可以执行的函数。

// 创建一个“打招呼”函数工厂
function createGreeter(greeting) {
  return function(name) {
    console.log(`${greeting}, ${name}!`);
  };
}

const sayHello = createGreeter('你好');
const sayHi    = createGreeter('Hi');

sayHello('小明');  // "你好, 小明!"
sayHi('John');     // "Hi, John!"

createGreeter 接收一个问候语,返回一个只需要提供名字就能完成打招呼行为的函数。这里的 greeting 被返回的函数“记住”了——这正是闭包在起作用。高阶函数和闭包是一对绝佳拍档,前者提供结构,后者提供内存。

实际开发中,这种模式经常用于封装通用逻辑,比如防抖和节流:

// 防抖:高频触发的事件,只在最后一次触发后延时执行
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 使用:输入框实时搜索,不再每次按键都发请求
input.addEventListener('input', debounce(handleSearch, 300));

debounce 接收一个原始函数和延迟时间,返回一个经过“防抖处理”的新函数。内部的定时器逻辑被封装在闭包中,对外只暴露一个纯净的函数接口。类似地,throttleonce(只执行一次)都可以用这种模式实现。

9.3.3 组合高阶函数:让逻辑像搭积木

当项目中频繁使用高阶函数时,逻辑可以被拆分成多个“函数处理器”,然后像管道一样串联起来:

function pipe(...fns) {
  return function(initialValue) {
    return fns.reduce((value, fn) => fn(value), initialValue);
  };
}

const add10 = n => n + 10;
const double = n => n * 2;
const add10ThenDouble = pipe(add10, double);

add10ThenDouble(5);  // (5 + 10) * 2 = 30

pipe 自身就是一个高阶函数(接受多个函数,返回一个组合函数),它将数据加工流程清晰地表达出来,避免了嵌套调用。

9.3.4 实战中常见的用途总结

| 用途 | 典型场景 | 使用的高阶函数形式 |
|------|---------|-------------------|
| 回调处理 | DOM 事件、Ajax、定时器、异步流程控制 | 函数作为参数 |
| 函数装饰器 | 防抖、节流、埋点、权限校验 | 函数作为返回值 |
| 柯里化与偏应用 | 提前配置参数,复用函数 | 函数作为返回值 |
| 遍历与聚合 | Array 的 filtermapreduce 等 | 函数作为参数 |
| 状态管理与中间件 | Redux、Express、Koa 的中间件链路 | 函数作为参数或返回值 |

9.3.5 为什么高阶函数如此重要

  • 减少重复代码:把变化的部分抽离成函数,固定逻辑只写一次。
  • 提高可读性:函数名通常比循环体更易理解,numbers.filter(isEven)for 循环加 if 更直观。
  • 便于测试:独立的函数可以单独测试,不受外层逻辑干扰。
  • 拥抱函数式风格:高阶函数是函数组合、柯里化、管道等范式的基石,也是 React Hooks 和现代状态管理库的思想来源。

当你掌握了函数作为参数和返回值这两种用法,就能写出更抽象、更通用的代码。试着在下次写 for 循环之前,先想想数组是否有现成的高阶方法可用;在重复编写相似的逻辑时,考虑是否能用一个返回函数的函数来消除冗余。这正是从“会写 JavaScript”到“写出优雅 JavaScript”的关键一步。