JavaScript 中处理和使用函数的方式非常有趣。它们非常灵活——我们可以将函数作为值分配给变量,将它们作为值从另一个函数返回,并将它们作为参数传递给另一个函数。我们可以做到这一切,因为 JavaScript 将函数视为一等公民。
在本文中,我将介绍什么是高阶函数和回调,以及它们在 JavaScript 中是如何工作的。
在 JavaScript 中充当一等公民
函数在 JavaScript 中被定义为一等公民或一等对象,因为函数被视为变量。
这意味着 JavaScript 中的函数可以是:
作为参数传递给另一个函数。
作为值分配给变量。
作为函数的值返回。
了解函数在 JavaScript 中的处理方式至关重要,因为它们是理解 JavaScript 中高阶函数和回调函数以及它们如何工作的构建块。
什么是高阶函数?
高阶函数是将函数作为参数并返回函数作为值的函数。
JavaScript 中提供了许多内置的高阶函数。我们将看一些,并利用如何将函数视为一等公民。我们还将创建自己的高阶函数。
首先,让我们看一些内置高阶函数的例子。
数组方法
数组方法通常是开发人员在学习 JavaScript 时首次引入的高阶函数。这些方法包括但不限于 JavaScript 提供的 、 、 、 和 数组方法。
这些数组方法或函数有很多共同点,但最常见的特征之一是它们都接受函数作为参数。下面是一个代码片段,演示了数组方法的工作原理:mapfilterforEachfindfindIndexsomeeveryforEach
const people = [
{ firstName: "Jack", year: 1988 },
{ name: "Kait", year: 1986 },
{ name: "Irv", year: 1970 },
{ name: "Lux", year: 2015 },];people.forEach(function (person) {
console.log(person);});// Output: Logs every person object in the array
从上面的代码示例中,我们可以看到该方法接受一个函数作为参数,它在数组上的每次迭代中调用该函数。因此,数组方法是一个高阶函数。forEachforEach
计时器事件
另一组常用的内置高阶函数是 and 函数,在 JavaScript 中称为计时器事件。setIntervalsetTimeout
每个函数都接受一个函数作为其参数之一,并使用它来创建时标事件。
看看下面的代码示例,看看它是如何工作的:setTimeout
setTimeout(function () {
console.log("This is a higher order function");}, 1000);// Output: "This is a higher order function" after 1000ms / 1 second
上面的代码片段是函数工作原理的最基本示例。它接受函数和持续时间(以毫秒为单位),并在提供的持续时间过后执行该函数。setTimeout
从上面的示例中,在 1000 毫秒或一秒后打印到控制台。This is a higher order function
setInterval(function () {
console.log("This is a higher order function");}, 1000);// Output: "This is a higher order function" after every 1000ms / 1 second
该函数类似于函数,就像数组方法一样——尽管它的功能不同。但是我们可以看到一个共同的模式:它也接受一个函数作为它的参数之一。setIntervalsetTimeout
与(在提供的持续时间过后执行函数)不同,每 1000 毫秒或 1 秒一遍又一遍地执行函数。setTimeoutsetInterval
如何创建和使用高阶函数
高阶函数不限于 JavaScript 提供的内置函数。
由于 JavaScript 中的函数被视为第一类对象,因此我们可以利用这种行为并构建高性能和可重用的函数。
在下面的示例中,我们将构建几个函数。他们将接受客户的姓名和问候语,然后将该信息打印到控制台。
首先,这里有一个简单的函数可以同时执行这两件事:
function greetCustomer(firstName, lastName, salutation) {
const fullName = `${firstName} ${lastName}`;
console.log(`${salutation} ${fullName}`);}greetCustomer("Franklin", "Okolie", "Good Day");// Output: "Good Day Franklin Okolie"
greetCustomer接受 3 个参数:名字、姓氏和称呼。然后,它将对客户的问候语打印到控制台。
但是这个功能有一个问题——它做两件事:撰写客户的全名和打印问候语。
这不是最佳实践,因为函数应该只做一件事并把它做好。因此,我们将重构我们的代码。
另一个函数应包含客户的姓名,以便该函数只需将问候语打印到控制台。因此,让我们编写一个函数来处理这个问题:greetCustomer
function composeName(firstName, lastName) {
const fullName = `${firstName} ${lastName}`;
return fullName;}
现在我们有一个结合了客户名字和姓氏的函数,我们可以在以下位置使用该函数:greetCustomer
function greetCustomer(composerFunc, firstName, lastName, salutation) {
const fullName = composerFunc(firstName, lastName);
console.log(`${salutation} ${fullName}`);}greetCustomer(composeName, "Franklin", "Okolie", "Good Day");// Output: "Good Day Franklin Okolie"
现在这看起来更干净了,每个函数只做一件事。该函数现在接受 4 个参数,并且由于其中一个参数是函数,因此它现在是一个高阶函数。greetCustomer
您之前可能想知道,一个函数是如何在另一个函数内部调用的,为什么?
现在,我们将深入探讨函数调用并回答这两个问题。
将函数作为值返回
请记住,高阶函数要么将函数作为参数,要么将函数作为值返回。
让我们重构函数以使用更少的参数并返回一个函数:greetCustomer
function getGreetingsDetails(composerFunc, salutation) {
return function greetCustomer(firstName, lastName) {
const fullName = composerFunc(firstName, lastName);
console.log(`${salutation} ${fullName}`);
};
最后一个版本接受了太多的论点。四个论点并不多,但如果你搞砸了论点的顺序,仍然会令人沮丧。一般来说,你的论点越少越好。greetCustomer
因此,在上面的示例中,我们有一个名为 Function 的函数,它接受并代表内部函数。然后,它返回内部函数,该函数本身接受 和 作为参数。getGreetingDetailscomposerFuncsalutationgreetCustomergreetCustomerfirstNamelastName
通过这样做,总体上有更少的论点。greetCustomer
有了这个,让我们来看看如何使用该函数:getGreetingDetails
const greet = getGreetingsDetails(composeName, "Happy New Year!");greet("Quincy", "Larson");// Output: "Happy New Year Quincy Larson"
现在退后一步,欣赏这个美丽的抽象。奇妙!我们利用高阶函数的魔力来简化函数。greetCustomer
让我们来看看一切是如何工作的。名为 Named 的高阶函数包含两个参数:一个用于组成客户名字和姓氏的函数,以及一个称呼。然后,它返回一个名为的函数,该函数接受客户的名字和姓氏作为参数。getGreetingDetailsgreetCustomer
返回的函数也使用接受的参数来执行某些操作。greetCustomergetGreetingDetails
在这一点上,您可能想知道,返回的函数如何使用提供给父函数的参数?特别是考虑到函数执行上下文的工作方式。这是可能的,因为关闭。现在让我们更多地了解它们。
闭合解释
闭包是一个函数,它有权访问创建它的范围内的变量,即使该范围在执行上下文中不再存在。这是回调的基本机制之一,因为在外部函数关闭后,回调仍然可以引用和使用在外部函数中创建的变量。
让我们举一个简单的例子:
function getTwoNumbers(num1, num2) {
return function add() {
const total = num1 + num2;
console.log(total);
};}const addNumbers = getTwoNumbers(5, 2);addNumbers();//Output: 7;
此示例中的代码定义了一个名为的函数,并演示了闭包的工作原理。让我们更详细地探讨一下:getTwoNumbers
getTwoNumbers定义为采用两个参数的函数,并且 .num1num2
在里面,它返回另一个函数,这是一个名为 的内部函数。getTwoNumbersadd
调用该函数时,会计算 和 的总和,并将结果记录到控制台。addnum1num2
在函数之外,我们创建一个变量,调用它,并为其分配调用的结果。这有效地设置了一个闭包,现在“记住”值和 as 和 .getTwoNumbersaddNumbersgetTwoNumbers(5, 2)addNumbers52num1num2
最后,我们调用执行内部函数。由于是闭包,因此它仍然可以访问分别设置为 和 的值。它计算它们的总和并记录到控制台。addNumbers()addaddNumbersnum1num2527
如果您想了解有关关闭的更多信息,请在此处阅读更多内容。
回到我们的高阶函数。返回的函数作为值返回,我们将其存储在名为 的变量中。greetCustomergreet
这样做会使变量本身成为一个函数,这意味着我们可以将其作为函数调用并传入名字和姓氏的参数。greet
还有 violà 你有它。这些概念一开始可能有点复杂,但一旦你掌握了它们的窍门,它们就永远不会离开你。
我鼓励您再次通读前面的部分,在编辑器中播放代码,并掌握所有内容如何协同工作的窍门。
现在您已经深入了解了高阶函数的工作原理,让我们来谈谈回调函数。
什么是回调函数?
回调函数是作为参数传递到另一个函数的函数。
同样,作为一等公民的函数的决定性因素之一是它能够作为参数传递给另一个函数。这称为传递回调的行为。
让我们回过头来看看我们之前在学习 JavaScript 中提供的内置函数时讨论的计时事件。这又是函数:setTimeout
setTimeout(function () {
console.log("This is a higher order function");}, 1000);// Output: "This is a higher order function" after 1000ms / 1 seconds
我们已经确定该函数是一个高阶函数,因为它接受另一个函数作为参数。setTimeout
作为参数传递给函数的函数称为回调函数。这是因为它是在它传入的高阶函数中调用或执行的。setTimeout
为了更好地理解回调函数,让我们再看一下前面的函数:greetCustomer
// THIS IS A CALLBACK FUNCTION// IT IS PASSED AS AN ARGUMENT TO A FUNCTIONfunction composeName(firstName, lastName) {
const fullName = `${firstName} ${lastName}`;
return fullName;}// THIS IS A HIGHER ORDER FUNCTION// IT ACCPEPTS A FUNCTION AS A ARGUMENTfunction greetCustomer(composerFunc, firstName, lastName, salutation) {
const fullName = composerFunc(firstName, lastName);
console.log(`${salutation} ${fullName}`);}greetCustomer(composeName, "Franklin", "Okolie", "Good Day");// Output: "Good Day Franklin Okolie"
是一个回调函数,它作为参数传递到函数中,这是一个高阶函数,并在此函数中执行。composeNamegreetCustomer
高阶函数和回调函数的区别
重要的是要了解这两个术语之间的区别,这样我们才能在技术面试中与队友进行更清晰的沟通:
高阶函数:接受函数作为参数和/或返回函数作为其值的函数。
回调函数:作为参数传递给另一个函数的函数。
一个包和一本书
为了进一步理解这些术语,我将分享一个简单的类比。
想象一下,你有一个包和一本书。你在参加聚会、上课、去教堂等时把书放在包里。
在这种情况下,袋子会接受您的书来携带它,并在您想使用它时将其归还。所以袋子就像一个高阶函数。
这本书一直放在袋子里,直到它准备好使用,所以它就像一个回调函数。
燃油和油箱
让我们看另一个类比;燃料和油箱。
为了给汽车加油,我们必须将燃料倒入油箱,油箱接收燃料——就像高阶函数一样。
燃油被倒入油箱 - 就像一个回调函数。
我希望这些类比有助于进一步简化高阶函数和回调函数以及它们之间的区别。
结论
正如你所看到的,JavaScript中的函数非常灵活,可以以很多有用的方式使用。这种灵活性也导致了 JavaScript 中两个常见的技术术语,高阶函数和回调函数。