回调函数(Callback Function)是 JavaScript 中一个特殊的编程模式,尤其是在处理异步函数的时候。它可以让你把一个函数作为参数传递给另一个函数,并在特定的时候调用这个函数。
回调函数
我们通过一个函数来了解回调函数。
只需要创建一个接受 name 参数的函数 greet(name)。这个函数应该返回打招呼的消息:
function greet(name){
return `Hello,${name}!`;
}
greet('Cristina'); // => 'Hello Cristina!'
但如果同时向很多人打招呼该怎么办?这就可以利用 array.map() 来实现:
const persons = ['Cristina','Ana'];
const messages = person.map(greet);
message; // => ['Hello,Cristina!','Hello,Ana!']
person.map(greet) 获取 person 数组中的所有元素,并分别用每个元素作为调用参数来调用 greet() 函数:greet('Cristina'),greet('Ana')。
有意思的是 person.map(greet) 方法可以接受 greet() 函数作为参数。这样 greet() 就变成了回调函数。
而 person.map(greet) 是用另一个函数作为参数的函数,因此被称为高阶函数。重要的是,高阶函数负责调用回调,并提供正确的参数。
注意 : 回调函数是高阶函数的参数,而高阶函数通过调用回调函数来执行操作。
在先前的例子中,高阶函数 person.map(greet) 调用 greet() 函数,并分别把数组中的所有元素作为参数。
这就为辨别回调函数提供了一个简单的规则。当将一个函数作为参数传递给另一个函数,并在适当的时候调用它。这种机制就叫回调。
我们可以尝试编写一个使用回调的高阶函数,来检验下是否听懂:
function map(array,callback){
const mappedArray = [];
for(const item of array){
mappedArray.push(
callback(item)
);
}
return mappedArray;
}
function greet(name){
return `Hello,¥{name}!`;
}
const person = ['Cristina','Ana'];
const message = map (persons,greet);
messages;// => ['Hello,Cristina!','Hello,Ana!']
这里写的是 array.map() 的等效版本,map(array,callback) 是一个高阶函数,因为它把回调函数作为参数,并在主体内部调用该函数:callback(item).
注意,常规函数(用 function 关键字定义)或箭头函数(用 => 定义)同样可以作为回调函数使用。
同步回调
回调的调用方式有两种:同步和异步。
同步回调函数会在主函数的执行流中被立即执行,而不是在未来的某个时间点(如异步操作完成)。这意味着主函数会暂停执行,直到回调函数执行完成后才会继续。
例如:
function map(array,callback){
console.log('map() starts');
const mappedArray = [];
for(const item of array{
mappedArray.push(callback(item))
}
console.log('map() completed');
return mappedArray;
}
function greet(name){
console.log('greet() called');
return `Hello,${name}!`;
}
const persons = ['Cristina'];
map(persons,greet)
我们在这里调用了 map() 和 greet() 函数,其中 greet() 是同步回调。
同步回调的执行步骤是:
- 高阶函数开始执行:
'map() starts'。 - 回调函数执行:
'greet() called'。 - 最后高阶函数完成自己的过程:
'map() completed'。
同步回调的实际应用
同步回调在实际场景中有许多应用:
数据处理
function transformData(data, transformCallback) {
console.log('Original data:', data);
const transformedData = transformCallback(data);
console.log('Transformed data:', transformedData);
}
function toUpperCase(str) {
return str.toUpperCase();
}
transformData('hello world', toUpperCase);
// 输出:
// Original data: hello world
// Transformed data: HELLO WORLD
格式化
function checkUserInput(input, validationCallback) {
if (validationCallback(input)) {
console.log('Input is valid');
} else {
console.log('Input is invalid');
}
}
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+.[^\s@]+$/;
return emailRegex.test(email);
}
checkUserInput('test@example.com', isValidEmail); // 输出: Input is valid
checkUserInput('invalid-email', isValidEmail); // 输出: Input is invalid
注意: 当利用回调函数来传递时,可能会遇到上下文丢失的问题,尤其是在类的方法中。这时可以使用箭头函数或 bind() 方法来解决。
异步回调
与同步回调不同,异步回调函数不会立即执行,而是在未来的某一时刻(如网络请求完成、定时器等)才会调用。这种特殊的机制使得程序可以在等待异步操作的时候能继续执行其他任务,从而提高效率和响应性。
在接下来的这个例子中,我们让 later() 函数的执行延迟了2秒:
console.log('setTimeout() starts');
setTime(fuhnction later(){
console.log('later() called');
},2000);
console.log('setTimeout() completed');
later() 是一个异步回调函数,因为 setTimeout(later,2000) 启动并完成了执行,所以 later() 在2秒后执行。
异步回调的执行步骤:
- 高阶函数开始执行:
'setTimeout() starts'。 - 高阶函数执行:
'setTimeout() completed'。 - 回调函数在2秒后执行:
'later() called'。
异步回调的实际应用场景
异步回调通常用于处理那些需要一些时间才能解决的操作,例如:
- 网络请求
- 文件系统操作
- 定时器
- 数据库查询
- AJAX请求
定时器
function doSomethingAsync(callback) {
console.log('Starting async operation...');
setTimeout(() => {
console.log('Async operation completed.');
callback();
}, 1000); // 模拟异步操作,延迟1秒
}
function onComplete() {
console.log('Operation finished.');
}
doSomethingAsync(onComplete);
异步回调函数和异步函数
在定义函数之前添加关键字 async 就会创建一个异步函数:
async function fetchUserNames(){
const resp = await fetch('https://api.github.com/users?per_page=5');
const users = await resp.json();
const names = users.map(({login})=> login);
console.log(names);
}
在这里我们可以明显看出,fetchUserNames() 是异步的,因为它有 async 前缀。await fetch('https://api.github.com/users?per_page=5'); 则是从 GitHub 中获取5个用户数据,然后通过 await resp.json() 来提取出JSON数据。
异步函数是 promise 上的语法糖。当遇见表达式 await<promise>(调用fetch()时会返回一个 promise)时,异步函数就会暂停执行,直到 promise 完成。
要记住,异步回调函数和异步函数是两个不同的概念。异步回调函数是由高阶函数以非阻塞方式来执行。但异步函数要等待 promise 完成解析后才会接着执行。
但我们可以把异步函数当做异步回调来使用。
总结
回调就是一个函数作为参数传递给另一个函数。
回调函数有两种:同步和异步。同步回调会在调用时发生阻塞,要等回调函数运行完后主函数才会运行;而异步则是会等到某个时间点在执行,不会阻塞主函数执行。