理解回调
理解回调
理解回调
对于编程新手来说,回调似乎是一个难题。简而言之,回调就是将函数作为参数传递给另一个函数。JavaScript 中定义函数的方式多种多样,难怪回调容易让人困惑。
函数的剖析
JavaScript 有很多定义函数的方法,但它们都遵循类似的模式,包含相同的部分,只是外观略有不同。函数相关的技术术语比较多,但我们暂时先略过。(如果您感兴趣,可以查阅“函数声明”和“函数表达式”)。
普通函数(命名函数)
普通函数可能是你学习创建函数的第一个方法。理解普通函数的结构将有助于你理解其他类型的函数。
function funkyFunction(music, isWhiteBoy) {
if (isWhiteBoy) {
console.log('Play: ' + music);
}
}
这实际上被称为function declaration
并被分成几个部分。
- 关键词
function
- 这告诉 JavaScript 编译器你正在创建一个命名函数
- 名字
- 这是函数的名称,也是调用该函数时会用到的名称。它也用于堆栈跟踪。
- 参数
(
和之间的所有内容都是)
参数,如果有多个参数,则必须用逗号分隔。如果函数不接受任何参数,则 和 之间也可以没有任何内容()
。括号是必需的。
- 函数主体
- 这是函数实际执行操作的地方。这段代码会根据传入参数的值运行。
调用函数与声明函数类似。调用函数时,输入函数名称并()
在其后添加 . (无需function
关键字和函数体)。在 . 内部,()
您可以传递您希望定义的参数所代表的值。这些值arguments
在函数体中用作变量。
// Calling a function
funkyFunction('that funky music', true);
// This prints "Play: that funky music" in the terminal.
匿名函数
这些函数与普通函数非常相似,只有一些区别。匿名函数没有“名称”,并且语法也有一些不同。即使它们没有名称,也可以赋值给变量。即使赋值给变量后它们会显示在堆栈跟踪中,它们仍然被视为匿名函数。但是,当它们作为回调传递给其他函数时,它们可能会在堆栈跟踪中显示为“匿名函数”。
匿名函数最常见的用法是将其作为 传递给其他函数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);
}
}
匿名函数只是没有名称的函数,但这并不意味着它不能被调用。上述每个函数都可以用完全相同的方式调用:
funkyFunction('that funky music', true);
这是因为函数是 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')`
下面是一些不带参数的箭头函数的示例。这些函数也完全相同。请注意,()
所有命名参数都用 代替。这是必需的,因为没有任何参数。
const playThat = () => "funky music";
const playThat = () => { return "funky music"; }
const playThat = () => {
return "funky music";
}
关键点
花一些时间研究上面的函数示例,并注意它们的相似之处以及除了function
关键字之外两者中存在的相同部分。
回调是什么样子的
你很可能见过,甚至使用过回调,只是没有意识到而已。回调在 JavaScript 中被频繁使用。如果不理解回调,就不可能理解 JavaScript。下面是一个你可能之前遇到过的例子。
const notes = ['do', 're', 'me'];
notes.forEach((note) => console.log(note));
这是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);
回调如何工作
再说一遍:回调只是作为参数传递给其他函数的函数。
迭代器函数
下面是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);
})
哇哦,等等。你是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);
回调如何工作的另一个很好的例子可能是.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)
事件监听器(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.
});
如果你注意到,第二个参数(传递给函数的值)addEventListener
是一个函数。在本例中,它是一个匿名箭头函数。这段代码也可以这样写,并且行为完全相同。
const element = document.querySelector("#myId");
element.addEventListener('click', function(event) {
console.log(event.target.value);
});
让人们感到困惑的部分原因在于event
物体本身。它从哪里来?又是如何到达那里的?
该事件对象由函数传入回调函数.addEventListener
。一个函数正在调用另一个函数。
这是因为...回调只是作为参数传递给另一个函数的函数。
这意味着我们可以在参数列表之外声明一个函数,并且只需通过其名称添加即可。像这样:
function myEventHandler(event) {
// do something, probably with 'event'
}
const element = document.querySelector("#myId");
element.addEventListener('click', myEventHandler);
注意,我们并没有“调用”这个函数myEventHandler
。如果我们在参数列表中调用它,那么我们调用的函数myEventHandler
会立即运行,并返回addEventListener
调用该函数的结果。(在这种情况下,结果将是 undefined)
结论
回调函数是 JavaScript 的重要组成部分,理解它至关重要,即使在 Promise 和 Async/Await 出现之后也是如此。回调函数会被另一个函数调用,因此你不必在参数中直接调用它们(调用函数就是使用函数名并()
在其末尾添加 ,例如console.log()
)。
只要你给自己时间,你就能学到这些东西,了解它们的工作原理将使你的 JavaScript 职业生涯变得轻松很多!
文章来源:https://dev.to/i3uckwheat/understanding-callbacks-2o9e