这几天和我的同事聊到这个问题,他提出了一些深入的问题,想了想,我也有相同的疑问。真是验证了那句话,了解的越多就发现自己了解的越少!so 被迫研究了下这个问题总结起来。便于以后查看和补充。
因为多线程会产生共享资源,修改彼此,等复杂问题。并且早期 js
只是来显示简单页面的,但今非昔比,或许 js
作者可能都没想到现在的 js
发展的如此迅猛!所以单线程更适合 js
,一直延续至今。那问题来了,只有单线程是如何处理耗时任务且实现不阻塞的呢,这里就用到了事件循环。事件循环(event loop)是一种计算机系统运行机制。作为单线程的 js
来说,事件循环是来解决当执行耗时任务时不阻塞主线程的调度机制。来自维基百科的解释
以当前最新版 chrome_v98.0 浏览器为准
js
中的任务可分为
打开一个 tab 页面,浏览器会新开辟一个进程。包含执行栈( js
主执行线程)和一些其他线程(属于浏览器)。当一个包含 css, js
的 html 页面被加载时,会从上往下,从左到右按顺序解析。当遇到同步任务时,它的执行上下文会被放入执行栈,执行完毕后,出栈。下一个放入,依次执行。
当遇到耗时(异步)任务,会根据不同的任务类型,提供给专门的线程来处理,举出一些例子,不限于这些:
等到这些异步任务应该被执行时,比如用户点击按钮、网络请求、延时器时间到了等,会把相应的回调函数放入异步队列中,按先后顺序排列起来待命。
直到执行栈的所有同步任务执行完毕后,同步栈会从异步队列中第一位开始取出入栈,依次执行。如果每个异步队列中的任务被执行的过程中,再次产生异步任务,会被放到任务队列的最后,等待执行。
这些专门线程处理一些任务时候,拿到结果后,都按照先后顺序放入队列被执行,没有办法控制任务优先级,希望一些任务不在队列最末尾排队,而是直接插队执行。为了解决这个问题,在异步任务队列中再细分成两种类型,微任务和宏任务,微任务可以理解是插队的任务,优先级更高。根据类型的不同会被放入不同的队列:
按照先微再宏的顺序执行:每一个宏任务开始前,都先要把上一个宏任务产生的微任务执行完毕再开始新的宏任务,微任务是宏任务的一部分,换句话来说,就是当执行栈空了之后,先检查微任务队列,再检查宏任务队列。
更通熟易懂事件循环动画参考 B 站
基于 chrome_v98.0 浏览器环境
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
解析:按照从上往下顺序先执行同步代码,前两个函数async和async2是函数声明,忽略。然后第 1 个输出的是script start,然后看到setTimeout, 由于它是异步且属于宏任务,放到最后执行。然后看到 async1 函数被执行了,进入async1函数体。
第 2 个输出的是async1 start, 看到 await 规则是:await 右的语句被同步执行,所以第 3 个输出的是async2,await 下的语句(函数体内)被当作 async 函数返回一个 promsise 的回调函数体的语句去处理,且被放入了异步任务队列中,等待中。
然后跳出 async1 函数体,继续执行同步代码,遇到 new Promise,第 4 次输出promise,看到 resolve,就把 then 中回调,放入任务队列中,然后继续向下执行同步代码,第 5 次输出的最后的script end。
至此同步执行栈空了,开始把任务队列中的上下文拿到执行栈执行,由于队列先进先出,所以第一个被入栈的是 async1 的回调,也就是第 6 次输出的是async1 end,然后把第二个队列中的上下文放入执行栈,也就是 promise 的 resolve,所以第 7 次输出promise2,至此微任务执行完成,现在执行宏任务,也就是 timeout,所以第 8 次输出为setTimeout。
最终结果
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
计算机底层 · 2022年3月6日 · 被浏览 - 次