本文围绕以下几点展开讨论
什么是事件环(Event Loop)?
事件环是如何运行的?
首先,我们要了解,浏览器是多进程的,每一个Tab网页都是一个进程,每一个进程里都存在多个功能线程,接下来只取两个线程进行讨论(JS引擎线程和GUI渲染线程)。
JS引擎线程:负责解析和执行JavaScript脚本,一个tab只有一个JS引擎线程,也就是单线程。
GUI渲染线程:负责浏览器页面HTML的渲染和绘制,页面发生重绘或回流,该线程会执行
两个线程之间的关系是互斥的,其中一个线程在执行时,另一个线程必定是挂起的。
一丶 那什么是事件环(Event Loop)?
我们先对JS的运行进行几个点的划分:
JS执行栈:对应的是< script >全局执行上下文。
异步任务执行队列:先进先出的存储模式(队列),分为两种任务,如下
宏任务:setTimeout / setInterval / setImmediate
微任务:promise.then / mutationObserver / process.nextTick
GUI渲染:渲染页面。
简单说:
1 - JS执行全局上下文时,会将异步任务分别放入宏任务队列和微任务任务队列。
2 - 待同步任务执行完毕以后,先清空微任务队列内的任务。
3 - GUI渲染线程执行,渲染页面。
4 - 宏任务队列取出第一个任务的回调(callBack),放入JS执行栈。
5 - 回到第一步,继续按以上顺序执行。
总结 - 这就是JavaScript事件环。
二丶 结合demo讲解事件环的运行?
var p = new Promise(resolve => {
console.log(1);
resolve(2);
})
function fn1() {
console.log(3);
}
function fn2() {
// 宏任务1 setTimeout1
setTimeout(() => {
console.log(4);
});
fn1();
console.log(5);
//微任务1 p.then1
p.then(resolve => {
console.log(resolve);
})
//微任务2 p.then2
.then(() => {
console.log(6);
})
}
fn2();
/**
* 第一步
* 全局执行上下文 执行阶段
* 1 - new Promise 代码块是同步执行的 执行console.log(1);
* 2 - 执行fn2();
* 3 - 存在延时器 setTimeout 直接给宏任务队列 添加 任务1 setTimeout1的回调
* 4 - 执行fn1(); 执行console.log(3);
* 5 - console.log(5);
* 6 - 存在 p.then 直接给 微任务队列 添加任务 微任务1 p.then1的回调 微任务2 p.then2的回调
* 执行完毕: 输出 1 -> 3 ->5
*
*
* 宏任务队列
* 任务1:setTimeout1的回调
*
* 微任务队列
* p.then1 的回调
* p.then2 的回调
*/
/**
* 第二步
* 清空微任务队列,
* 1: 将 微任务的 回调函数 放入 JS执行栈执行
* console.log(resolve);
* console.log(6);
* 2: 执行结果 分别是 2 -> 6
* 3: 微任务队列清空
*
*
* 宏任务队列
* 任务1:setTimeout1的回调
*
* 微任务队列
*/
/**
* 第三步
* 执行GUI线程,渲染页面
*
* 宏任务队列
* 任务1:setTimeout1的回调
*
* 微任务队列:空
*/
/**
* 第四步
* 1 - 从宏任务队列中,取出第一个宏任务的回调,放入 JS执行栈
* console.log(4);
* 宏任务队列:空
*
* 微任务队列:空
*/
/**
* 第五步
* 1 - JS执行栈 执行代码
* console.log(4);
*
* 2 - 执行结果 输出 4
* 宏任务队列:空
*
* 微任务队列:空
*/
/**
* 之后的 延续 第二步 执行 形成一个 闭环循环。
* 最后结果 输出 1 3 5 2 6 4
*/