理解回调 理解回调

2025-05-28

理解回调

理解回调

理解回调

对于编程新手来说,回调似乎是一个难题。简而言之,回调就是将函数作为参数传递给另一个函数。JavaScript 中定义函数的方式多种多样,难怪回调容易让人困惑。

函数的剖析

JavaScript 有很多定义函数的方法,但它们都遵循类似的模式,包含相同的部分,只是外观略有不同。函数相关的技术术语比较多,但我们暂时先略过。(如果您感兴趣,可以查阅“函数声明”和“函数表达式”)。

普通函数(命名函数)

普通函数可能是你学习创建函数的第一个方法。理解普通函数的结构将有助于你理解其他类型的函数。

function funkyFunction(music, isWhiteBoy) {
  if (isWhiteBoy) {
    console.log('Play: ' +  music);
  }
}
Enter fullscreen mode Exit fullscreen mode

这实际上被称为function declaration并被分成几个部分。

  1. 关键词function
    • 这告诉 JavaScript 编译器你正在创建一个命名函数
  2. 名字
    • 这是函数的名称,也是调用该函数时会用到的名称。它也用于堆栈跟踪。
  3. 参数
    • (之间的所有内容都是)参数,如果有多个参数,则必须用逗号分隔。如果函数不接受任何参数,则 和 之间也可以没有任何内容()。括号是必需的。
  4. 函数主体
    • 这是函数实际执行操作的地方。这段代码会根据传入参数的值运行。

调用函数与声明函数类似。调用函数时,输入函数名称并()在其后添加 . (无需function关键字和函数体)。在 . 内部,()您可以传递您希望定义的参数所代表的值。这些值arguments在函数体中用作变量。

// Calling a function
funkyFunction('that funky music', true);

// This prints "Play: that funky music" in the terminal.
Enter fullscreen mode Exit fullscreen mode

匿名函数

这些函数与普通函数非常相似,只有一些区别。匿名函数没有“名称”,并且语法也有一些不同。即使它们没有名称,也可以赋值给变量。即使赋值给变量后它们会显示在堆栈跟踪中,它们仍然被视为匿名函数。但是,当它们作为回调传递给其他函数时,它们可能会在堆栈跟踪中显示为“匿名函数”。

匿名函数最常见的用法是将其作为 传递给其他函数callback。这一点稍后会更加清晰。

下面的每个函数都与上面的 funkyFunction 的“功能性”相同

// This example is still an anonymous function even though we used the `function` keyword, as it doesn't have a name.
const funkyFunction = function(music, isWhiteBoy) {
  if (isWhiteBoy) {
    console.log('Play: ' +  music);
  }
}

// This is called an arrow function, we'll get into these soon.
const funkyFunction = (music, isWhiteBoy) => {
  if (isWhiteBoy) {
    console.log('Play: ' +  music);
  }
}
Enter fullscreen mode Exit fullscreen mode

匿名函数只是没有名称的函数,但这并不意味着它不能被调用。上述每个函数都可以用完全相同的方式调用:

funkyFunction('that funky music', true);
Enter fullscreen mode Exit fullscreen mode

这是因为函数是 JavaScript 中的“一等公民”,可以分配给变量,或者作为参数传递给另一个函数。

箭头函数

这些只是编写函数的更简洁方法。然而,它们确实有一些特殊的规则,理解箭头函数所施加的规则将有助于你理解回调。我们暂时先忽略this这些函数的绑定规则。

  • 如果只有一个参数,则()可以省略括号
  • 如果箭头函数只有一行,则{}可以省略括号。
    • 当省略括号时,箭头函数返回计算的表达式而不需要return关键字。

以下函数是上述规则的变体

const playThe = (funky) => {
  return funky + " music";
}

const playThe = funky => {
  return funky + " music";
}

const playThe = funky => funky + " music";

// You can call all of these functions like: `playThe('blues')`
Enter fullscreen mode Exit fullscreen mode

下面是一些不带参数的箭头函数的示例。这些函数也完全相同。请注意,()所有命名参数都用 代替。这是必需的,因为没有任何参数。

const playThat = () => "funky music";

const playThat = () => { return "funky music"; }

const playThat = () => {
  return "funky music";
}
Enter fullscreen mode Exit fullscreen mode

关键点

花一些时间研究上面的函数示例,并注意它们的相似之处以及除了function关键字之外两者中存在的相同部分。

回调是什么样子的

你很可能见过,甚至使用过回调,只是没有意识到而已。回调在 JavaScript 中被频繁使用。如果不理解回调,就不可能理解 JavaScript。下面是一个你可能之前遇到过的例子。

const notes = ['do', 're', 'me'];

notes.forEach((note) => console.log(note));
Enter fullscreen mode Exit fullscreen mode

这是forEach数组方法。此方法仅接受一个callback函数作为参数。(别忘了,它forEach本身也是一个函数)。

还有许多其他方法可以做同样的事情(这是 JavaScript 中的传统),下面是编写此代码的几种方法:

const notes = ['do', 'ray', 'me'];

notes.forEach((note) => { 
  console.log(note);
});

notes.forEach(function(note) {
  console.log(note); 
});

// This one is tricky, but will make more sense later
notes.forEach(console.log); 
Enter fullscreen mode Exit fullscreen mode

回调如何工作

再说一遍:回调只是作为参数传递给其他函数的函数。

迭代器函数

下面是forEach其内部结构,请注意它callback在每次循环一个项目时都会调用该函数。

function myForEach(array, callback) {
  for (let i = 0; i < array.length; i++) {
    callback(array[i]); // This is when the callback function gets called, or executed
  }
}

// You would call it like this:
const myArry = [2, 3, 4, 2];
myForEach(myArry, (item) => {
  console.log(item + 2); 
})
Enter fullscreen mode Exit fullscreen mode

哇哦,等等。你是item从哪儿来的?

这源于函数myForEach调用带参数的回调函数。这一行代码callback(array[i])调用了带参数的回调函数,我们将其内联定义为匿名函数。下面是更多调用示例。

const myArry = [2, 3, 4, 2];

// We do not need the `()` in this case, as we only have one argument and we are using an arrow function
myForEach(myArry, item => console.log(item + 2)); 

// We can pass arguments to this kind of anonymous function as well
myForEach(myArry, function(item) {  
  console.log(item + 2) 
});

// This time we are declaring the function we want to use as a callback
// Notice we define `item` as a parameter to be passed in when it's called by the `myForEach` function.
function printItemPlusTwo(item) {
  console.log(item + 2);
}

// `item` is passed into the function, we do not need to declare it here because we declared it elsewhere. 
// It is the same as the 'console.log' example above except we declared our own function.
myForEach(myArry, printItemPlusTwo); 
Enter fullscreen mode Exit fullscreen mode

回调如何工作的另一个很好的例子可能是.map方法(在 MDN 上阅读更多内容),下面是它可能实现的一种方式。

function myMap(array, callback) {
  const myNewArray = [];

  for (let i = 0; i < array.length; i++) {
    const callbackResult = callback(array[i]);
    myNewArray.push(callbackResult); 
  }

  return myNewArray;
}


// This could be called like this:
const addedArray = myMap([1, 2, 3], (arrayNum) => {
  return arrayNum + 2; 
});


// OR
const addedArray = myMap([1, 2, 3], (arrayNum) => arrayNum + 2)
Enter fullscreen mode Exit fullscreen mode

事件监听器(DOM)

JavaScript 中的事件监听器似乎让人们感到困惑,但在理解了回调之后,这些应该会更容易理解。

让我们回顾一下它们的样子,看看你是否能找出正在发生的不同的事情。

const element = document.querySelector("#myId");
element.addEventListener('click', (event) => {
  console.log(event.target.value);
  // `event` is passed into the callback from the `.addEventListener` function when it receives a 'click' event.
});
Enter fullscreen mode Exit fullscreen mode

如果你注意到,第二个参数(传递给函数的值)addEventListener是一个函数。在本例中,它是一个匿名箭头函数。这段代码也可以这样写,并且行为完全相同。

const element = document.querySelector("#myId");
element.addEventListener('click', function(event) {
  console.log(event.target.value);
});
Enter fullscreen mode Exit fullscreen mode

让人们感到困惑的部分原因在于event物体本身。它从哪里来?又是如何到达那里的?

该事件对象由函数传入回调函数.addEventListener。一个函数正在调用另一个函数。

这是因为...回调只是作为参数传递给另一个函数的函数。

这意味着我们可以在参数列表之外声明一个函数,并且只需通过其名称添加即可。像这样:

function myEventHandler(event) {
  // do something, probably with 'event'
}

const element = document.querySelector("#myId");
element.addEventListener('click', myEventHandler);
Enter fullscreen mode Exit fullscreen mode

注意,我们并没有“调用”这个函数myEventHandler。如果我们在参数列表中调用它,那么我们调用的函数myEventHandler会立即运行,并返回addEventListener调用该函数的结果。(在这种情况下,结果将是 undefined)

结论

回调函数是 JavaScript 的重要组成部分,理解它至关重要,即使在 Promise 和 Async/Await 出现之后也是如此。回调函数会被另一个函数调用,因此你不必在参数中直接调用它们(调用函数就是使用函数名并()在其末尾添加 ,例如console.log())。

只要你给自己时间,你就能学到这些东西,了解它们的工作原理将使你的 JavaScript 职业生涯变得轻松很多!

文章来源:https://dev.to/i3uckwheat/understanding-callbacks-2o9e
PREV
自定义 React Hooks:useBoolean
NEXT
我的 2021 年学习计划 超棒算法