知识线
JS 为什么是单线程? => 单线程带来的问题?=> 如何实现异步编程?(同步和异步) =>异步代码的执行顺序 => EventLoop运行机制。
JS 为什么是单线程?
JS 单线程的特点就是同一时刻只能执行一个任务。
这是由一些与
用户的互动以及操作 DOM 等相关的操作
决定了 JS 要使用单线程,否则使用多线程会带来 复杂的同步问题。如果是多线程,一个线程正在修改 DOM,另一个线程正在删除 DOM,那么以哪一 个为准呢?如果执行同步问题的话,多线程需要加锁,执行任务造成非常的繁琐
注意:HTML5标准规定,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM 。
单线程带来的问题?
由于 JavaScript 是单线程的,单线程就意味着
阻塞问题
,当一个任务执行完成之后才能执行下一个任务。这样就会导致出现页面卡死的状态,页面无响应,影响用户的体验,所以不得不出现了同步任务和异步任务
的解决方案。
同步任务
: 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。异步任务
: 不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
如何实现异步编程?
为了解决上述问题,出现了异步任务执行,它的运行机制如下:
- 所有同步任务都在主线程上执行,形成一个
执行栈
。 - 主线程之外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个 事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些 对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步
注意:在主线程读取任务队列的时候,任务队列是一种“先进先出”的数据结构,排在前面的事 件,优先被主线程读取。执行栈只要执行空,任务队列的第一个事件进入主线程。同时,如果存 在定时器,主线程需要检查定时器执行的时间,到了时间才能给主线程执行。
面试官:JS 如何实现异步编程?
回调函数
回调函数就是作为异步任务的执行。异步任务必须指定回调函数。
回调函数不是直接调用,而是在特定的事件或条件发生时另一方调用的,用于对该事件或条件进行响 应。比如 Ajax 回调:
// jQuery 中的 ajax
$.ajax({
type : "post",
url : 'test.json',
dataType : 'json',
success : function(res) {
// 响应成功回调
},
fail: function(err){
// 响应失败回调
}
});
但是如果某个请求存在依赖性,如下:
$.ajax({
type: "post",
success: function (res) {//成功回调
//再次异步请求
$.ajax({
type: "post",
url: "...?id=res.id,
success: function (res) {
$.ajax({
type: "post",
url: "...?id=res.id,
success: function () {
// 往复循环
}
})
}
})
}
})
就会形成不断的循环嵌套,我们称之为回调地狱
。我们可以看出回调地狱有以下缺点:
- 嵌套函数存在耦合性,一旦有所改动,牵一发而动全身。
- 嵌套函数一多,就很难处理错误。
- 回调函数不能使用
try catch
捕获异常(异常的捕获只能在函数执行的时候才能捕获到)。 - 回调函数不能直接
return
。
为什么不能捕获异常?
其实这跟 js 的运行机制相关,异步任务执行完成会加入任务队列,当执行栈中没有可执行任务了,主 线程取出任务队列中的异步任务并入栈执行,当异步任务执行的时候,捕获异常的函数已经在执行栈内 退出了,所以异常无法被捕获。
为什么不能 return?
return
只能终止回调的函数的执行,而不能终止外部代码的执行。
解决回调地狱问题
面试官:如何解决回调地狱问题呢?
可以参考 promise详解–传送门 、es6笔记之promise–传送门