高阶函数是接受函数作为参数或返回函数作为结果的函数,体现JavaScript函数是一等公民;如map、filter、reduce及手写的once、memoize,核心在于运行时传入/返回函数值而非语法糖。

高阶函数不是“高级写法”,而是指**接受函数作为参数,或返回函数作为结果的函数**——这是 JavaScript 中函数是一等公民的直接体现,不是语法糖,也不是可选技巧。
高阶函数的核心判断标准:typeof 为 "function" 的值能否被传入或返回
只要满足以下任一条件,就是高阶函数:
- 参数中至少有一个是函数(如
Array.prototype.map的第一个参数) - 函数体里用
return返回了一个新函数(如const add = (a) => (b) => a + b) - 不关心函数名或是否具名,只看运行时行为:传入/返回的是函数值
常见误判:把闭包、箭头函数、异步回调本身当成高阶函数——它们只是函数,只有当被当作参数传给另一个函数,或被另一个函数返回时,才参与构成高阶函数调用链。
map、filter、reduce 是最常用的内置高阶函数
它们本身不执行具体逻辑,而是把“怎么处理每个元素”的决策权交给传入的回调函数。这正是高阶函数的价值:解耦数据遍历与业务逻辑。
立即学习“Java免费学习笔记(深入)”;
例如:
const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2); //n => n * 2是传入的函数 const evens = numbers.filter(n => n % 2 === 0); //n => n % 2 === 0是传入的函数 const sum = numbers.reduce((acc, n) => acc + n, 0); //(acc, n) => acc + n是传入的函数
注意:map 和 filter 不修改原数组;reduce 的初始值(第二个参数)不能省略,否则第一次调用时 acc 会是数组第一个元素,容易出错。
手写一个高阶函数:带缓存的 once 和 memoize
这两个是典型场景:控制函数执行次数(once),或复用已有计算结果(memoize)。它们必须返回新函数,才能拦截原始调用。
const once = (fn) => {
let called = false;
let result;
return function(...args) {
if (!called) {
result = fn.apply(this, args);
called = true;
}
return result;
};
};
const memoize = (fn) => {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
};
关键点:
-
once内部用闭包保存called和result,确保多次调用返回同一结果 -
memoize用JSON.stringify(args)做简易键生成,仅适用于参数可序列化的场景;若含函数、undefined、Symbol,需改用更健壮的哈希方案 - 两者都用
...args和fn.apply(this, args)保证this和参数透传,否则会丢失上下文
容易被忽略的陷阱:this 绑定、参数数量、副作用时机
高阶函数不是“套个壳就完事”。下面这些错误在真实项目中高频出现:
- 用
obj.method.map(...)时,method被提取后丢失this,应写成obj.method.bind(obj)或(...args) => obj.method(...args) - 传入的回调函数参数数量不匹配:比如
array.forEach(callback)传了 3 个参数,但callback只声明了 1 个形参,剩余参数会被静默丢弃 - 在高阶函数内部提前执行了副作用(如发请求、改 DOM),而不是把副作用封装进返回的函数里,导致逻辑失控
真正难的不是写出高阶函数,而是判断该在哪一层做抽象、哪些状态该由闭包捕获、哪些该由调用方传入——这需要对数据流和生命周期有清晰把握。











