JavaScript中的高阶函数 - 初学者指南

103 阅读5分钟

在JavaScript中,函数被当作第一等公民对待。我们可以把函数当作值,并把它们赋给另一个变量,把它们作为参数传给另一个函数,甚至从另一个函数返回。

函数的这种作为一级函数的能力是JavaScript中高阶函数的力量所在。

基本上,一个将另一个函数作为参数或返回一个函数的函数被称为高阶函数。

Group-35

让我们深入研究一下,看看这两种类型的实现,也就是:

  • 将一个函数作为参数传递给另一个函数
  • 从另一个函数中返回一个函数

63eec0636ec9b999bf8c5ee5340dd54a_w200

如何将一个函数作为参数传递给另一个函数

在本节中,我们将看到如何将一个函数作为参数来发送,以及最终如何帮助我们写出更干净的代码。

考虑一下下面的代码,我们想创建一个接受数组作为参数的函数。它过滤掉其中的所有奇数,并返回所有过滤后的数字。

这个函数将看起来像这样:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

function filterOdd(arr) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] % 2 !== 0) {
      filteredArr.push(arr[i]);
    }
  }
  return filteredArr;
}
console.log(filterOdd(arr));

// Output:
// [ 1, 3, 5, 7, 9, 11 ]

上述函数返回过滤后的数组[ 1, 3, 5, 7, 9, 11 ] ,其中我们有所有的奇数,正如预期的那样。

现在,让我们假设我们也想做一个过滤掉并返回所有偶数的函数。我们完全可以继续创建以下函数来实现这一目的:

function filterEven(arr) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] % 2 == 0) {
      filteredArr.push(arr[i]);
    }
  }
  return filteredArr;
}
console.log(filterEven(arr));

// Output:
// [ 2, 4, 6, 8, 10 ]

同样,正如预期的那样,我们将得到期望的输出,即一个包含所有偶数的数组--[ 2, 4, 6, 8, 10 ]

但是请注意,我们在这种方法中写了很多重复的代码。上述两个函数都做了很多共同的事情,比如接受原始数组,创建一个新的数组来存储过滤后的数组,在整个主数组中循环,最后返回过滤后的数组。

这两个函数的唯一区别是它们用来过滤原始数组的逻辑。

对于函数filterOdd ,我们使用的是arr[i] % 2 !== 0 的逻辑,而在函数filterEven ,我们使用的是arr[i] % 2 == 0 的逻辑来过滤掉原始数组。

这就是我们可以从使用高阶函数中获益的地方。主要意图是创建一个函数来完成我们在上面两个函数中所做的所有常见的事情,并将逻辑部分作为参数单独传递给这个函数。让我们看看如何实现这一点。

让我们创建一个函数来完成我们在filterOddfilterEven 函数中执行的所有常见的东西。这将会是这样的:

function filterFunction(arr, callback) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i]) ? filteredArr.push(arr[i]) : null;
  }
  return filteredArr;
}

暂时忽略callback 的参数。请注意,在新的filterFuntion 中,我们保留了所有常见的步骤,即接受原始数组,创建一个新的数组来存储过滤后的数组,循环浏览整个主数组,最后返回我们在filterOddfilterEven 函数中执行的过滤后的数组。

现在,callback 参数基本上接受了这个逻辑,这将是另一个包含过滤逻辑的函数。为了分别过滤奇数和偶数,这里是我们需要写的逻辑函数:

// Function containing logic for filtering out odd numbers

function isOdd(x) {
  return x % 2 != 0;
}

// Function containing logic for filtering out even numbers

function isEven(x) {
  return x % 2 === 0;
}

就这样了!我们现在只需要像这样把主数组和逻辑函数一起传递给我们的filterFunction

// For filtering out odd numbers

filterFunction(arr, isOdd)
// Output of console.log(filterFunction(arr, isOdd)):
// [ 1, 3, 5, 7, 9, 11 ]

// For filtering out even numbers

filterFunction(arr, isEven)
// Output of console.log(filterFunction(arr, isEven)):
// [ 2, 4, 6, 8, 10 ]

这样我们就把逻辑函数如isOddisEven 作为参数传给另一个函数filterFunction

我们基本上是把主要的过滤逻辑从主函数中抽象出来了。现在我们可以将任何其他的过滤逻辑传递给filterFunction ,而不需要改变它。

例如,如果我们想过滤掉一个大于5的数字,那么我们只需要编写以下过滤逻辑:

function isGreaterThanFive(x) {
  return x > 5;
}

并将其作为参数传递给filterFunction

filterFunction(arr, isGreaterThanFive)

// Output of console.log(filterFunction(arr, isGreaterThanFive)):
// [ 6, 7, 8, 9, 10, 11 ]

我们也可以把逻辑函数作为一个箭头函数来传递,得到同样的结果--也就是说,把(x) => x > 5) 代替isGreaterThanFive ,会得到同样的结果:

filterFunction(arr, (x) => x > 5)

// Output of console.log(filterFunction(arr, (x) => x > 5)):
// [ 6, 7, 8, 9, 10, 11 ]

如何创建Polyfills

我们知道,JavaScript为我们提供了一些内置的高阶函数,如map()filter()reduce() 等。我们可以重新创建我们自己的这些函数的实现吗?让我们再深入研究一下。

我们已经在上面的章节中创建了我们的过滤函数。让我们为我们的filterFunction 函数创建一个数组原型,这样我们就可以在任何数组中使用它。这将看起来像这样:

Array.prototype.filterFunction = function (callback) {
  const filteredArr = [];
  for (let i = 0; i < this.length; i++) {
    callback(this[i]) ? filteredArr.push(this[i]) : null;
  }
  return filteredArr;
};

在上面的代码中,this 指的是原型被调用的数组。因此,如果我们写成这样:

const arr = [1, 2, 3, 4, 5]
arr.filterFunction(callbackFn)

那么this 将指代数组arr

现在我们可以使用filterFunction ,就像我们在JS中使用内置的filter() 函数。我们可以这样写:

arr.filterFunction(isEven)

这类似于调用内置的filter() 函数:

arr.filter(isEven)

上述两个函数的调用(即arr.filterFunction(isEven)arr.filter(isEven) )将给我们带来相同的输出,如[ 2, 4, 6, 8, 10 ]

同样,我们也可以在我们的原型实现中传递一个箭头函数,正如我们可以传递内置的filter() 函数一样:

// I
arr.filterFunction((x) => x % 2 != 0)
arr.filter((x) => x % 2 != 0)
// both give the same output on console.log: [ 1, 3, 5, 7, 9, 11 ]

// II
arr.filterFunction((x) => x > 5)
arr.filter((x) => x > 5)
// both give the same output on console.log: [ 6, 7, 8, 9, 10, 11 ]

在某种程度上,我们已经为内置的filter() 函数写了一个polyfill。

函数链

我们也可以用我们的原型实现函数链,就像我们可以用内置的filter() 函数一样。让我们首先过滤掉所有大于5的数字。然后从结果中,我们将过滤掉所有的偶数。它看起来会是这样的:

// Using our own filterFunction() prototype implementation
arr.filterFunction((x) => x > 5).filterFunction((x) => x % 2 === 0)

//Using the inbuilt filter() implementation
arr.filter((x) => x > 5).filter((x) => x % 2 === 0)

// both give the same output on console.log: [ 6, 8, 10 ]

这就是我们如何在JS中使用高阶函数来编写模式模块化、更干净、更可维护的代码。

接下来,让我们看看如何从另一个函数中返回一个函数:

lets-move-on-proceed

如何在JavaScript中从另一个函数返回一个函数

我们可以从另一个函数中返回一个函数,因为我们在JavaScript中把函数当作值。让我们通过一个例子来看看这一点:

function calculate(operation) {
  switch (operation) {
    case "ADD":
      return function (a, b) {
        console.log(`${a} + ${b} = ${a + b}`);
      };
    case "SUBTRACT":
      return function (a, b) {
        console.log(`${a} - ${b} = ${a - b}`);
      };
  }
}

在上面的代码中,当我们用一个参数调用函数calculate ,它将切换到该参数,然后最后返回一个匿名函数。因此,如果我们调用函数calculate() ,并将其结果存储在一个变量中,然后在控制台记录,我们将得到以下输出:

const calculateAdd = calculate("ADD");
console.log(calculateAdd);

// Output: 
// [Function (anonymous)]

你可以看到,calculateAdd 包含一个匿名函数,而calculate() 函数返回了这个匿名函数。

有两种方法来调用这个内部函数,我们现在来探讨一下。

使用一个变量调用返回的函数

在这个方法中,我们将返回的函数存储在一个变量中,如上图所示,然后调用这个变量来反过来调用这个内部函数。

让我们在代码中看到它:

const calculateAdd = calculate("ADD");
calculateAdd(2, 3);
// Output: 2 + 3 = 5


const calculateSubtract = calculate("SUBTRACT");
calculateSubtract(2, 3);
// Output: 2 - 3 = -1

那么,我们在这里做了什么?

  • 我们调用了calculate() 函数,并将ADD 作为参数传入
  • 我们将返回的匿名函数存储在calculateAdd 变量中,并且
  • 我们通过调用带有所需参数的calculateAdd() 来调用内部返回的函数

使用双括号调用返回的函数

这是一种非常复杂的调用内部返回函数的方式。我们在这个方法中使用双括号()()

让我们在代码中看到它:

calculate("ADD")(2, 3);
// Output: 2 + 3 = 5

calculate("SUBTRACT")(2, 3);
// Output: 2 - 3 = -1

你可以用类似于我们上面的链式例子的方式来思考这个问题。只是,我们不是在链接函数,而是在链接参数。

第一个括号中的参数属于外层函数,而第二个括号中的参数属于内部返回的函数。

calculate() 方法返回一个函数,正如前面解释的那样,正是这个返回的函数被立即用第二个括号调用。

正如我上面提到的,这是一种非常复杂的调用函数的方式。但是一旦你掌握了它,它就会变得......很自然。

我们可以看到这种双括号符号的一个地方是在redux 状态管理库中的connect 方法。你可以在这里阅读更多关于connect

总结

在这篇文章中,我们了解到:

  • 为什么函数在JS中被称为第一等公民
  • 什么是高阶函数
  • 如何将一个函数作为一个参数传递给另一个函数
  • 如何创建一个数组原型,函数链,为内置的filter()方法编写我们自己的polyfill
  • 如何从另一个函数中返回一个函数以及调用返回函数的不同方法

总结

谢谢你的阅读!我真的希望你觉得这篇关于高阶函数的文章有用。请继续关注更多精彩内容。祝你平安!