_回调_函数是作为实参传给其他函数的函数。之后可以调用这个回调函数,以触发后续的操作。回调常用来处理已完成工作的结果,或在事件发生时处理相应的操作。回调函数既可以用于同步编程,也可以用于异步编程。
const sideLength = 5;
// Caller function takes a callback function
function applySideLength(callback) {
return callback(sideLength);
}
// Callback must expect the possible argument from the calling function
function areaOfSquare(side) {
return side * side;
}
applySideLength(areaOfSquare); // => 25
你也可以把回调写成函数表达式:
applySideLength(function squarePerimeter(side) {
return side * 4;
});
这种模式在 JavaScript 中很有用,因为 JavaScript 被设计成单线程的运行时,同一时刻只能执行一个函数调用。在执行期间,运行时无法响应其他事件,也无法继续执行,直到该函数返回为止。
很多 API 调用(I/O 函数、定时器和事件监听器)会使用异步机制,把当前函数调用先放到一边,直到工作完成。完成后,回调函数会被放入消息队列,这样当运行时处于两次函数调用之间时,就会通过调用回调函数来处理这些消息。
使用回调函数还有一个好处:它可以引用自己闭包作用域中的变量,而稍后调用它的那个函数无法访问这些变量。
事件处理器是 JavaScript 中回调最常见的用途之一。像'onload'或'onclick'这样的浏览器事件,是可以触发函数调用的信号。一个 DOM [文档对象模型对象的addEventListener方法会注册一个回调函数,当它“听到”某个事件已经发生时,就会调用这个回调。
document.addEventListener('onload', function () {
alert('The webpage has now been loaded');
});
在 Node.js 中,回调函数遵循一套惯例来控制程序的流程。它们遵循这样的模式:回调函数的第一个实参可能收到一个 Error 或 null;第二个及后面的实参接收调用函数要发送的数据。
如果发生错误,第二个及后面的实参可能不存在,所以不能指望它们一定存在。
function operation(a, b, callback) {
// Work ...
if (/* an error occurs */) {
return callback(new Error('An error occurred'))
}
// On success:
callback(null, data)
}
function callback(error, returnedData) {
if (error) {
// An error occurred, handle it here.
return
}
// No error occurred, continue with the returned data.
}
operation(1, 2, callback)
在处理异步函数以协助控制流程时,你会经常看到这种模式。
常见的 Array 函数使用回调函数来定义自己的行为:
Array.prototype.forEach:
接收一个回调,并对数组中的每个元素应用这个回调。
[1, 2, 3].forEach(function (element) {
doSomething(element);
});
// => doSomething() is invoked 3 times, once with each element
Array.prototype.map
接收一个回调,对数组中的每个元素应用这个回调,并用结果创建一个新数组。
[1, 2, 3].map(function (element) {
return element + 1;
});
// => [2, 3, 4]
Array.prototype.reduce
接收一个回调,对数组中的每个元素应用这个回调,并把结果传给下一次调用。
[1, 2, 3].reduce(function (runningSum, element) {
return runningSum + element;
}, 0);
// => 6