浏览器事件循环
浏览器是一个多进程多线程的应用程序
为了避免相互影响,减少连环崩坏的几率,当启动浏览器后,会自动启动多个进程(每一个标签页都会开启一个进程,所以说chrome是内存杀手)
主要进程包括
- 浏览器进程:界面显示,用户交互,子进程管理
- 网络进程:加载网络资源
- 渲染进程:会开启一个渲染主线程,处理html,css,js
渲染主线程如何工作
渲染进程要处理的事情非常多,包括但不限于
- 解析html
- 解析css
- 计算样式
- 执行js
- 处理事件回调函数
- 处理定时器函数
- …
这么多任务渲染主线程如何调度?
-
渲染主线程会进入无限循环。
-
每次循环会检查消息队列中是否存在任务 --> 有则取出第1个任务执行–> 执行完进入下次循环。没有任务则进入休眠状态。
-
其他所有线程(包括其他进程的子线程),可以随时向消息队列的末尾添加任务。
添加任务时,如果渲染主线程是休眠状态,则会唤醒让它继续循环取出任务执行。
注意:消息队列不止一个,常见包括 微任务(最高),交互队列(高),延时队列(中)
微任务: promise.then(), await下面, queuemicrotask()
其他队列(宏任务):ajax,settimeout,dom监听,ui渲染
以上过程称为事件循环
异步
在执行代码时,会遇到一些无法立即处理的任务。
- 计时器结束时需要执行的任务。—— setTimeout setInterval
- 网络通信完成后需要执行的任务。—— XHR Fetch
- 用于操作后需要执行的任务。—— EventListener
如果渲染主线程一直等待这些任务的时间点到来,那就会一直处于阻塞的状态,表现为浏览器卡死。
浏览器选择用异步来解决这个问题。这样渲染主线程永不阻塞。
同步 JS 为什么会阻塞渲染
因为 js 运行在渲染主线程中,所以是单线程的语言。而渲染主线程有许多的工作,包括渲染页面和执行全局 js。
问题
1,如何理解js异步
因为 js 运行在渲染主线程中,所以是单线程的语言。而渲染主线程有许多的工作,包括渲染页面和执行全局 js。
如果使用同步的方式,大概率会造成渲染主线程阻塞,进而导致消息队列中的很多其他任务无法及时执行。表现为页面无法及时更新,给用户造成浏览器卡死的现象。
所以浏览器采用异步的方式来避免这个问题。具体做法:
当有某些任务产生后,计时器,网络通信,事件监听等,渲染主线程会交给其他对应线程去处理,自身继续执行后面的代码。当其他线程处理完成后,会将实现传递的回调函数包装为任务,发送到消息队列末尾,等待渲染主线程调度执行。
在这种异步模式下,浏览器不会被阻塞,最大限度的保证了单线程的流畅运行。
2,讲一下 js 的事件循环
事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。
在 Chrome 的源码中,会开启一个不会结束的 for 循环for(;{},每次循环从消息队列中取出第1个任务执行,其他线程将产生的任务加入到消息队列末尾即可。
过去把消息队列简单的分为微队列和宏队列。现在已经无法满足现代浏览器的复杂环境了。
根据 W3C 的解释,每个任务都有类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。而不同队列的优先级也不一样。
一次事件循环中,由浏览器来决定调取那个队里中的任务。同时必须有一个优先级最高的微队列,必须优先调度执行。
3,js 能做到精准计时吗
不能。
1,受事件循环的影响,计时器的回调函数所在的延时队列优先级较低,这样即便倒计时结束,也得等某些队列的任务执行完成,所以会带来偏差。
2,W3C 的标准,浏览器实现的倒计时,如果嵌套超过5层,默认会有 4ms 的最少时间,这也是可能的偏差。
3,操作系统的计时函数本身就有少量误差,js 计时器调用的就是它,所以也会有偏差。
4,计算机硬件没有原子钟,无法做到精准计时。
文章评论