JavaScript回调函数:同步回调与异步回调
作者:admin 时间:2022-6-27 15:51:8 浏览:JavaScript 回调函数,是将函数作为参数传递给另一个函数,然后可以在另一个函数中调用该函数。回调有很多好处,它们开辟了很多编程可能性。回调方式也不是唯一的,我们可以同步回调,也可以异步回调,这就是我今天要说的内容。
同步回调
许多人第一次接触回调是在他们了解到可以为同一个排序算法提供不同的比较函数时。例如,当使用Array.prototype.sort()
方法对整数数组进行排序时,可选参数是比较函数compareFn
。
let arr1 = [22, 25, 55, 66, 23, 15, 1, 12]
arr1.sort() // arr1按升序排序
// arr1 变成 [1, 12, 15, 22, 23, 25, 55, 66]
let arr2 = […arr1] // 复制 arr1 到 arr2
arr2.sort((e1, e2)=> e2-e1) // arr2 按降序排序
// arr2 变成 [66, 55, 25, 23, 22, 15, 12, 1 ]
// (e1, e2)=> e2-e1 是比较函数
这种回调在大多数编程语言中都有,达到多态算法的效果。
上面说明了同步回调是如何工作的。同步回调在使用它们的高阶函数内部执行。当高阶函数完成执行时,其回调参数的执行也完成。由于高阶函数必须等待同步回调执行完成,所以同步回调也称为阻塞回调——回调的执行会阻塞调用者函数的执行。
JavaScript 中同步回调的一些其他示例是用于迭代数组的方法:forEach
、map
、filter
、reduce
、some
、every
等。
let arr = [1,2,3,4,5]
let arrDoubled = arr.map(e=>e+e)
console.log(arrDoubled) // 输出 [ 2, 4, 6, 8, 10 ]
异步回调
如果说同步回调是实现更大编程灵活性的方法,那么异步回调是实现更高性能和用户体验的方法。
异步回调的强大之处在于 JavaScript 独特的运行时模型。JavaScript 是一种单线程语言,也就是说 JavaScript 的执行引擎只有一个调用栈。
那么异步执行是如何实现的呢?
神奇之处在于 JavaScript 运行时环境的 API 处理程序。对于 Web 浏览器,API 是 Web API;对于 Node.js,API 是 I/O API。执行异步回调的任务被放入回调队列。
在调用堆栈中的现有代码运行完成后,事件循环(作为 JavaScript 引擎的一部分的进程)将回调队列中的回调带入执行。
一旦执行引擎运行回调,它会在下一个回调开始运行之前再次运行到完成(直到调用堆栈为空)。调用堆栈上的代码的这种运行到完成一直持续到队列中的所有回调都被执行为止。
异步方面来自这样一个事实,即回调不是在高阶函数中立即执行,而是放在回调队列中等待轮到它在调用堆栈上运行。
高阶函数是派发回调任务的函数,而不是运行它的函数。
使用异步回调最普遍的例子是使用setTimeOut
方法。
console.log("setTimeout 之前")
setTimeout(
()=>{ console.log("这里是2秒后的结果") },
2000
)
console.log("setTimeout 之后")
第一个参数是回调函数,第二个参数是等待的时间,以毫秒为单位。setTimeout
无需等待回调完成即可返回。
以下是输出的样子:
setTimeout 之前
setTimeout 之后
这里是2秒后的结果
异步回调机制的好处
异步回调机制的好处是所有同步代码都不会被异步事件阻塞。异步事件(例如对远程服务器的 AJAX 请求)可能需要一些时间才能运行。通过异步回调,Web 应用程序可以更流畅地运行且响应速度更快。
示例:同步回调转换为异步回调
同步回调
console.log('start');
function getGreeting(name, cb) {
cb(`Hello ${name}`);
}
console.log('before getGreeting');
getGreeting('WebKaka', (greeting) => {
console.log(greeting);
});
console.log('end');
输出
start
before getGreeting
Hello WebKaka
end
该程序从顶部开始,并在到达底部时顺序执行每一行。
异步回调
我们可以把上面的例子改为异步回调。
console.log('start');
function getGreetingAsync(name, cb) {
setTimeout(() => {
cb(`Hello ${name}`);
}, 0);
}
console.log('before getGreetingAsync');
getGreetingAsync('WebKaka', (greeting) => {
console.log(greeting);
});
console.log('end');
输出
start
before getGreetingAsync
end
Hello WebKaka
通过添加 setTimeout
,我们将回调函数的执行推迟到稍后的时间点。回调函数只有在程序从上到下执行完代码后才会运行(即使延迟为0ms)。
同步回调和异步回调之间的主要区别在于同步回调立即执行,而异步回调的执行推迟到稍后的时间点。
如何判断回调是同步还是异步?
回调是同步执行还是异步执行取决于调用它的函数。如果函数是异步的,那么回调也是异步的。
异步函数通常是执行网络请求、等待 I/O 操作(如鼠标单击)、与文件系统交互或向数据库发送查询的函数。这些函数的共同点是它们与当前程序之外的东西进行交互,并且你的应用程序一直等待直到响应返回。
相反,同步回调在程序的当前上下文中执行,与外界没有交互。你会在函数式编程中找到同步回调,例如,为集合中的每个项目调用回调(例如.filter()
、.map()
、.reduce()
等)。JavaScript 语言中的大多数原型方法都是同步的。
如果你不确定一个回调函数是同步执行还是异步执行,你可以在回调内部和之后添加console.log
语句,看看哪个先打印。
总结
本文介绍了JavaScript回调函数:同步回调与异步回调。无论是同步回调还是异步回调,都有各自的好处,在使用时需根据具体情况而选择采用何种编程方式。
相关文章
- 站长推荐