什么是高阶函式 (Higher order function)?使用高阶函式有什么好处?

2025年3月7日

💎 加入 E+ 成長計畫 如果你喜歡我們的內容,歡迎加入 E+,獲得更多深入的軟體前後端內容

相信有些人在一些面试考古题中,看过「什么是高阶函式? 为什么要用高阶函式?」等相关问题。在这篇文章中,我们将来探讨这个问题。

高阶函式不只是面试会被问,在日常工作上,也很常出现,推荐还不熟的读者要花些时间理解。

高阶函式 (Higher order function) 是什么?

高阶函式 (Higher order function) 是指当一个函式可以接受另一个函式作为参数、或者返回一个函式作为结果的函式。

这是一种函式语言程式设计 (functional programming) 的概念,不只 JavaScript,许多程式语言都有支援高阶函式,例如:Python、Swift 等。

它的重要性在于,可以帮助提高程式码的模块化和可维护性,因此很常应用到不同情境中,底下是一些例子:

  1. 回调函式 (callback function) 包装:可以将回调函式作为参数传递给另一个函式,以实现自定义的行为。
  2. 抽象操作:可以将高阶函式作为封装操作的一层抽象,例如实现过滤、转换等。
  3. 函式组合:可以使用高阶函式组合多个函式,以创建新的函式。

如果要直接背诵这题的回答,可以回说「高阶函式 (Higher order function) 是指可以接受另一个函式作为参数、或者会回传一个函式作为结果的函式」。

而当要进一步回说「为什么要用高阶函式」时,则可以回说「透过高阶函式,程式码的可读性可以提升,且能够减少不必要的重复,同时也能够容易除错」。

假如你过去对高阶函式没有太多概念,推荐不要直接背这两个回答。以下我们会透过多数人很熟悉的高阶函式  map 、 reduce  以及  filter  作为范例切入,让读者们理解高阶函式在做什么,同时感受到高阶函式的好处。

内建的高阶函式 map

在多数的程式语言中,都有  map  这个内建的高阶函式。map  函式在做的事情很简单,一般来说会有两个参数,一个参数是函式,一个是某个可迭代的东西 (例如阵列),然后  map  会迭代过这个可迭代的东西,然后在每次迭代时把值套到函式当中。

这样讲起来可能有点抽象,让我们实际来看例子。以 Python 的内建  map  函式,官方文件的定义是 return an iterator that applies function to every item of iterable, yielding the results. 基本上与上面对  map  的定义是相同的。

让我们实际来看可以怎么在 Python 中用  map

l = [-1, -2, -3]
print(list(map(abs,l))) # [1, 2, 3]

以上面的例子来说,核心重点是  map(abs,l),这边  map  接收的第一个引数是  abs  是 Python 内建把数字转成绝对值得函式,第二个则是  [-1, -2, -3]  这个可迭代的列表,所以会把  -1-2-3  分别套到  abs  中,获得绝对值。因此当我们把  map  的结果再透过  list  转成列表后引出来,就会获得  [1, 2, 3]

再来看看多数前后端工程师都熟悉的 JavaScript,虽然  map  在 JavaScript 当中是  Array  物件的方法,但其概念仍属于高阶函式的范畴。

在 MDN 上的范例如下:

const array1 = [1, 4, 9, 16];

// Pass a function to map
const map1 = array1.map((x) => x * 2);

console.log(map1);
// Expected output: Array [2, 8, 18, 32]

可以看到,如上面对高阶函式的定义中谈到高阶函式是「接受另一个函式作为参数」的函式,这边的  map  接收了  (x) => x * 2  这个函式,因此可被归类在高阶函式当中。而也如前面对  map  的定义,在 JavaScript 的  map  会迭代过阵列,然后把传入的函式套到阵列中的每个元素。因此,这边原本的  array1  是  [1, 4, 9, 16] ,在每个元素都套上  (x) => x * 2,就会获得  [2, 8, 18, 32]

如何实作  map?

在了解完  map  这个高阶函式后,假如我们想要实作一个最阳春版本的  map,可以怎么做呢?

让我们看到下面这个最阳出的版本 (仅处理阵列,不考量其他可迭代物)。这个  map  会接收一个阵列以及函式,而在  map  里面会宣告一个  result  阵列,然后迭代过阵列时,把每个元素  item  丢到  callback  函式中,在把取得的结果放到  result,最后回传  result  即可。

// map 会接受一个阵列,以及一个回呼函式
function map(arr, callback) {
  // 先宣告一个最后要回传的阵列
  const result = [];
  // 用 for 回圈迭代过阵列
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    // 把每个阵列元素丢到 callback 函式,并把结果放到 result 当中
    result.push(callback(item));
  }
  return result;
}

重新理解高阶函式的好处

在了解完  map  在做什么,以及简单实作一个阳春版的  map  后,让我们重访「为什么要用高阶函式? 」这个问题。还记得,上面提到高阶函式的好处在于「程式码的可读性可以提升,且能够减少不必要的重复,同时也能够容易除错」。

以  map  的例子来说,当今天有「要迭代过阵列,然后用同样逻辑转换阵列中的元素」的需求时,就可以直接用  map,不需用每次都重新写类似的程式码。与此同时,可以把核心焦点放在最重要的逻辑上。当今天要维护的时候,也只需要去改最核心的逻辑即可,这样维护起来会简单很多。

上面谈到的  map  是属于「接收函式」的类型,还记得前面谈到,高阶函式的定义当中,除了「接收函式」之外,第二个定义是「当某个函式回传另一个函式」,这也会被称为高阶函式。

让我们来看一个具体的例子,是  LeetCode 2623 题,也是很常会在面试中出现的考题  memoize  函式的实作。这个函式会把过去已经运算过的输出存起来,如果之后有同样的输入,未来就不用再运算一次,而是直接从快取拿之前算过的。

具体来说,memoize 会接收一个函式,然后回传一个带有记忆化 (快取) 的函式。对输出的函式来说,如果某个输入已经被运算过,就不会重复运算,直接从快取回传即可。

// 举例来说,有个 sum 函式,会回传两个参数的相加
const sum = (a, b) => a + b;

// 今天如果被 memoize 后获得 memoizedSum,会有以下的作用
const memoizedSum = memoize(sum);
memoizedSum(2, 2); // 4 这是经过运算的
memoizedSum(2, 2); // 4 这是直接从快取拿的,不用再次运算

可以看到,透过  memoize  这个高阶函式,不管什么样的函式,如果想要记忆化,都不需用重新写一遍,而是可以直接用  memoize  来包即可,让可读性提高,同时减少不必要的重复。

备注:想了解这个  memoize  如何实作,可以参考 ExplainThis 写过的解答

阅读更多

上面透过  map  来讨论高阶函式是什么、为什么要用。关于高阶函式的更多内容,包含常见的  reduce  以及  filter,还有实务上运用高阶函式的案例,都在  E+ 成长计划的主题文有更深入的解说。

除了高阶函式这个常见的面试题,E+ 成长计划上个月也上架了《软体工程师求职全攻略》  这堂长 8 小时 57 分钟的课程,收录了过去我们协助读者们求职的精华重点,从如何写履历,到如何准备行为、技术面试,以及在过程中遇到挫折时如何调整心态。

有兴趣的读者欢迎加入  E+,在求职过程一起加油~

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們