JS执行流程:事件循环=>宏任务和微任务

搞懂JS的执行流程对复杂代码的理解和优化还是非常重要的。首先JS和node中都是基于事件循环的。

JS的单线程:我的理解是JS执行环境栈(ECS)中同一时刻只能执行一个任务,不能并行执行多个任务。所以JS是单线程的。但是JS依然可以执行异步代码,是因为浏览器多线程辅助执行了程序。

事件循环 就是ECS中多个状态(执行任务,等待任务,休眠)无限切换的循环。浏览器的多个线程协同工作(比如有的线程在监听ajax是否返回数据,有的线程在给定时器计时),保证ECS中单线程的执行任务。

引擎在执行任务时永远都不会进行DOM的渲染或者更改,如果一项任务执行时间过长,浏览器就会抛出"页面未响应"之类的警报。

微任务:一般指promise,MutationObserver
宏任务:微任务以外的就是宏任务了,比如:ajax请求,函数,事件,setTimeout,setInterval,requestAnimationFrame等

每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再执行其他的宏任务,或渲染,或进行其他任何操作。就是说微任务会在执行任何其他事件处理,或渲染,或执行任何其他宏任务之前完成。

知道什么是宏任务,什么是微任务,知道事件循环,知道JS引擎先执行同步代码,再执行微任务,再执行宏任务,无限循环执行。这样就能够理解JS的执行流程了。

JS是解释型语言,代码是从上往下,从左至右逐行逐字执行的。遇到同步代码就直接交给ECS执行,遇到宏任务就交给工作线程去工作,比如定时器开始计时,监听ajax是否返回数据等等。时间到了或者数据返回了就放到宏任务队列中,等待ECS执行。遇到微任务就放入到微任务队列中,等到ECS执行。同步代码执行完了之后先清空微任务列表,然后再执行宏任务。如此循环执行就是JS的执行流程。
接下来看几个例子:

一、

setTimeout(() => alert("timeout")); 
Promise.resolve() .then(() => alert("promise")); 
alert("code");
// 他的执行顺序是:先code,因为是同步代码(主程序),然后是promise,因为执行完了宏任务就会清空微任务队列,然后再执行timeout,因为他是宏任务。

二、 平常所说的异步,也不是说JS开了另外的线程去执行,而是等同步的代码执行完毕以后,再同步的执行那段所谓的异步代码。JS同一个时间节点只能做 一件事情 所以你设置的定时任务,不是严格按照你设想的时间执行。比如:

let d1 = Date.now();
let d2 = Date.now();
while(d2 - d1 < 500) { // 这里耽误了五秒钟
    d2 = Date.now();
}
setTimeout(() => {
    console.log("定时器任务"); // 所该输出是在10秒之后输出
}, 5000);

三、

// 异步任务计时是从什么时候开始算的呢?
let d1 = Date.now();
let d2 = Date.now();
setTimeout(() => { // 执行到宏任务 会交给其他线程去计时 到时间再放入到宏任务队列中等待执行
    console.log("定时器任务") // 会在六秒钟之后立即输出
}, 4000);
while(d2 - d1 < 6000) {
    d2 = Date.now();
} 

四、

const str = "主程序";

setTimeout(() => { // ①
    Promise.resolve().then(() => {
        console.log("第一个零延时定时任务中的微任务");
    })
    console.log("第一个零延时的定时任务");
    setTimeout(() => {
        console.log("第一个零延时定时任务中的零延时定时任务")
    });
});
setTimeout(() => { // ②
    console.log("我是第二个零延时的定时任务");
});
Promise.resolve().then(() => {
    console.log("我是外部的微任务");
})
console.log(str);
// 代码从上往下执行 遇到定时器① 就交给工作线程去计时 然后往下走 遇到了定时器② 也交给工作线程去计时 时间到了一次放入到宏任务队列中
// 往下走遇到了微任务Promise 放入微任务队列 再往下执行主程序 
// 所以先输出 一、主程序
// 主程序执行完了 要先清空微任务队列 所以 输出 二、我是外部的微任务 
// 清空完了微任务列表 就 开始执行宏任务队列中的宏任务 继续执行: 同步代码 => 清空微任务队列 => 宏任务队列中的宏任务 => 清空微任务队列 => ...
// 所以输出 三、第一个零延时的定时任务
// 输出 四、第一个零延时定时任务中的微任务
// 输出 五、我是第二个零延时的定时任务
// 最后输出 六、第一个零延时定时任务中的零延时定时任务
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容