浏览器事件循环机制
进程和线程
一个在内存中运行的应用程序,每个应用程序都会有自己的内存空间,一个进程可以有多个线程。
进程是操作系统分配资源的最小单位。
当进程被创建时,也会创建一个属于这个进程的主线程,当主线程被关闭,进程也随之关闭。
线程是程序执行的最小单位
浏览器的进程与线程
现代浏览器的复杂程度堪比操作系统了,这里简化的讨论浏览器的进程与线程
浏览器在启动时,会创建多个进程
浏览器进程
网络进程
渲染进程
每个Tab页都是一个进程(这是目前的模式,未来可能会推进一个网站一个进程的模式即
Site pre precess)
这种创建多个进程的方式有效的防止了连锁崩溃,一个标签页的崩溃不会影响到别的标签页
其中最重要的是渲染进程,渲染进程会开启渲染主线程
而渲染主线程承担的工作有很多很多,需要解析HTML、CSS、JS,而渲染主线程在同一时间只能干一件事。
所以说,JS是一个单线程执行的语言。
在渲染主线程中,有渲染引擎和JS解析引擎,谷歌的解析引擎叫v8,采用C++编写,使用了JIT(即时编译)。
JIT:在程序运行时将热点代码(频繁执行的代码)编译为机器码,后续执行直接运行编译后的高效机器码。
在渲染主线程创建后,会使用for(;;) 启动一个死循环,在这个死循环中,会不断的去拿去消息队列中的任务。
在前端开发中,通常绕不开一个问题就是 -> 异步
为什么要有异步? -> 为了解决单线程带来的阻塞问题
为了实现异步,于是有了事件循环。
事件循环
在W3C标准中,事件循环被译为
events loop,在v8的内部实现中,事件循环的变量名是messages loop
这里只讨论在解析JS时,事件循环的情况。
渲染主线程不可能去执行一些非常耗时的任务,其中有一些延迟执行的操作,网络请求的操作,如果采用同步,就会把线程阻塞住。
为了解决同步阻塞的问题,于是有了任务队列,我们可以把任务队列粗略的分为两种
宏任务队列
微任务队列
微任务队列中的任务优先级是最高的。
而宏任务队列也可以细分其他队列
交互任务队列
延迟任务队列
......
按照优先级从高到底的排序是:微任务队列、交互任务队列、延迟任务队列
当遇到<script>标签时,V8引擎启动,开始解析全局的JS,在解析全局的JS时,会把遇到的函数等包装成任务推入任务队列;等待全局的JS解释完后,渲染主线程再执行任务队列中的任务。
手动添加微任务:Promise.resolve().then(fn), 相对与宏任务队列,不管任务的推入先后顺序,只有当微任务队列的任务全部执行完毕后才会去执行宏任务队列的任务。
来看一下这段代码
function a() {
console.log("1");
Promise.resolve().then(function () {
console.log("2");
})
}
Promise.resolve().then(function () {
console.log("4");
});
setTimeout(() => {
console.log('3');
}, 1000);
console.log('5');
a();当解析到这段JS时,首先开始全局解析,执行的操作按顺序是
创建一个微任务,输出4
创建一个定时器任务,1000毫秒后输出3
输出5
调用a函数
a函数先 输出了1 ,再创建了一个微任务用于输出2
全局解析执行完毕后,任务队列是这样的
微任务队列:fn(输出4),fn(输出2)
宏任务队列:
延迟任务队列:fn(输出3)
接下来,微任务队列的任务优先被执行,控制台 输出4、输出2
最后等待计时器结束, 输出3
所以最后的输出结果是:5,1,4,2,3
