摘要: 在本教程中,您将了解 JavaScript 回调函数,包括同步和异步回调。
什么是回调函数
在 JavaScript 中,函数是一等公民。因此,您可以将一个函数作为参数传递给另一个函数。
根据定义,回调函数是您作为参数传递给另一个函数的函数,以便稍后执行。
以下定义了一个filter()
函数,它接受一个数组,并返回一个包含奇数的新数组。
function filter(numbers) {
let results = [];
for (const number of numbers) {
if (number % 2 != 0) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
console.log(filter(numbers));
Code language: JavaScript (javascript)
它是如何工作的。
- 首先,定义一个接受数字数组并返回包含奇数的新数组的
filter()
函数。 - 其次,定义一个包含奇数和偶数的
numbers
数组。 - 第三,调用
filter()
函数从numbers
数组中获取奇数,并输出结果。
如果您想返回一个包含偶数的数组,您需要修改filter()
函数。为了使filter()
函数更通用和可重用,您可以
- 首先,将
if
块中的逻辑提取出来,并将其封装在单独的函数中。 - 其次,将函数作为参数传递给
filter()
函数。
以下是更新后的代码
function isOdd(number) {
return number % 2 != 0;
}
function filter(numbers, fn) {
let results = [];
for (const number of numbers) {
if (fn(number)) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
console.log(filter(numbers, isOdd));
Code language: JavaScript (javascript)
结果是一样的。但是,您可以将任何接受参数并返回布尔值的函数传递给filter()
函数的第二个参数。
例如,您可以使用filter()
函数来返回一个包含偶数的数组,如下所示
function isOdd(number) {
return number % 2 != 0;
}
function isEven(number) {
return number % 2 == 0;
}
function filter(numbers, fn) {
let results = [];
for (const number of numbers) {
if (fn(number)) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
console.log(filter(numbers, isOdd));
console.log(filter(numbers, isEven));
Code language: JavaScript (javascript)
根据定义,isOdd
和isEven
是回调函数。因为filter()
函数接受一个函数作为参数,所以它被称为高阶函数。
回调函数可以是匿名函数,它是一个没有名称的函数,如下所示
function filter(numbers, callback) {
let results = [];
for (const number of numbers) {
if (callback(number)) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
let oddNumbers = filter(numbers, function (number) {
return number % 2 != 0;
});
console.log(oddNumbers);
Code language: JavaScript (javascript)
在本例中,我们将一个匿名函数传递给filter()
函数,而不是使用单独的函数。
在 ES6 中,您可以使用箭头函数,如下所示
function filter(numbers, callback) {
let results = [];
for (const number of numbers) {
if (callback(number)) {
results.push(number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
let oddNumbers = filter(numbers, (number) => number % 2 != 0);
console.log(oddNumbers);
Code language: JavaScript (javascript)
回调函数有两种类型:同步回调函数和异步回调函数。
同步回调函数
同步回调函数在使用回调函数的高阶函数执行期间执行。isOdd
和isEven
是同步回调函数的例子,因为它们在filter()
函数执行期间执行。
异步回调函数
异步回调函数在使用回调函数的高阶函数执行后执行。
异步意味着,如果 JavaScript 必须等待操作完成,它将在等待时执行其余的代码。
请注意,JavaScript 是一种单线程编程语言。它通过回调队列和事件循环来执行异步操作。
假设您需要开发一个脚本,该脚本从远程服务器下载图片,并在下载完成后处理它。
function download(url) {
// ...
}
function process(picture) {
// ...
}
download(url);
process(picture);
Code language: JavaScript (javascript)
但是,从远程服务器下载图片需要时间,这取决于网络速度和图片的大小。
以下download()
函数使用setTimeout()
函数来模拟网络请求
function download(url) {
setTimeout(() => {
// script to download the picture here
console.log(`Downloading ${url} ...`);
},1000);
}
Code language: JavaScript (javascript)
这段代码模拟了process()
函数
function process(picture) {
console.log(`Processing ${picture}`);
}
Code language: JavaScript (javascript)
当您执行以下代码时
let url = 'https://tutorial.javascript.ac.cn/pic.jpg';
download(url);
process(url);
Code language: JavaScript (javascript)
您将获得以下输出
Processing https://javascripttutorial.net/pic.jpg
Downloading https://javascripttutorial.net/pic.jpg ...
Code language: JavaScript (javascript)
这不是您期望的结果,因为process()
函数在download()
函数之前执行。正确的顺序应该是
- 下载图片,等待下载完成。
- 处理图片。
要解决这个问题,您可以将process()
函数传递给download()
函数,并在下载完成后在download()
函数内执行process()
函数,如下所示
function download(url, callback) {
setTimeout(() => {
// script to download the picture here
console.log(`Downloading ${url} ...`);
// process the picture once it is completed
callback(url);
}, 1000);
}
function process(picture) {
console.log(`Processing ${picture}`);
}
let url = 'https://wwww.javascripttutorial.net/pic.jpg';
download(url, process);
Code language: JavaScript (javascript)
输出
Downloading https://tutorial.javascript.ac.cn/pic.jpg ...
Processing https://tutorial.javascript.ac.cn/pic.jpg
Code language: JavaScript (javascript)
现在,它按预期工作了。
在本例中,process()
是一个传递给异步函数的回调函数。
当您使用回调函数来在异步操作完成后继续执行代码时,回调函数被称为异步回调函数。
为了使代码更简洁,您可以将process()
函数定义为匿名函数
function download(url, callback) {
setTimeout(() => {
// script to download the picture here
console.log(`Downloading ${url} ...`);
// process the picture once it is completed
callback(url);
}, 1000);
}
let url = 'https://tutorial.javascript.ac.cn/pic.jpg';
download(url, function(picture) {
console.log(`Processing ${picture}`);
});
Code language: JavaScript (javascript)
处理错误
download()
函数假设一切正常,并且没有考虑任何异常。以下代码引入了两个回调函数:success
和failure
,分别用于处理成功和失败情况
function download(url, success, failure) {
setTimeout(() => {
console.log(`Downloading the picture from ${url} ...`);
!url ? failure(url) : success(url);
}, 1000);
}
download(
'',
(url) => console.log(`Processing the picture ${url}`),
(url) => console.log(`The '${url}' is not valid`)
);
Code language: JavaScript (javascript)
嵌套回调函数和回调地狱
如何下载三张图片并依次处理它们?一种典型的方法是在回调函数内部调用download()
函数,如下所示
function download(url, callback) {
setTimeout(() => {
console.log(`Downloading ${url} ...`);
callback(url);
}, 1000);
}
const url1 = 'https://tutorial.javascript.ac.cn/pic1.jpg';
const url2 = 'https://tutorial.javascript.ac.cn/pic2.jpg';
const url3 = 'https://tutorial.javascript.ac.cn/pic3.jpg';
download(url1, function (url) {
console.log(`Processing ${url}`);
download(url2, function (url) {
console.log(`Processing ${url}`);
download(url3, function (url) {
console.log(`Processing ${url}`);
});
});
});
Code language: JavaScript (javascript)
输出
Downloading https://tutorial.javascript.ac.cn/pic1.jpg ...
Processing https://tutorial.javascript.ac.cn/pic1.jpg
Downloading https://tutorial.javascript.ac.cn/pic2.jpg ...
Processing https://tutorial.javascript.ac.cn/pic2.jpg
Downloading https://tutorial.javascript.ac.cn/pic3.jpg ...
Processing https://tutorial.javascript.ac.cn/pic3.jpg
Code language: JavaScript (javascript)
脚本工作正常。
但是,当复杂性显著增长时,这种回调策略无法很好地扩展。
在回调函数内嵌套多个异步函数被称为回调地狱
asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
....
});
});
});
});
});
Code language: JavaScript (javascript)
为了避免回调地狱,您可以使用Promise或async/await函数。
摘要
- 回调函数是作为参数传递给另一个函数的函数,以便稍后执行。
- 高阶函数是一个接受另一个函数作为参数的函数。
- 回调函数可以是同步的或异步的。