浏览器的事件循环和浏览器有关、w3c 叫 event loop 、谷歌浏览器叫 message loop
一、浏览器的进程模型
1-1、进程
程序运行需要内存空间,可以简单理解为进程 每个应用至少有一个进程,进程相互独立,进程之间通讯需要双方同意
1-2、线程
线程来负责运行程序的代码 一个进程至少有一个线程,所以进程开启后,自动创建一个线程来运行代码,该线程称为主线程 如果程序需要同时执行多块代码,主线程就会启动更多的线程,所以进程中可以包含多个线程
1-3、浏览器的进程模型
浏览器是一个应用程序,是一个多线程,多进程的应用程序,内部极其复杂 为了各个模块相互影响,避免一个模块崩溃牵连其他模块
- 浏览器进程:主要负责界面显示、用户交互、子进程管理等、浏览器进程内部会启动多个线程处理不同的任务
- 网络进程:负责加载网络资源,内部会启动多个线程处理不同的网络任务
- 渲染进程 一个标签页一个, 渲染进程启动后,会开启一个渲染主线程,主线程负责执行 HTML、 CSS 、JS 代码,默认情况下,浏览器会给每个标签页开启一个新的渲染线程,以保证不同标签页之间不相互影响
- GPU 进程
二、渲染主线程是如何工作的?
渲染主线程工作繁忙
- 解析 HTML
- 解析 CSS
- 计算样式
- 布局
- 处理图层
- 每秒页面画60次 fps
- 执行全局 JS 代码
- 执行事件处理函数
- 执行计时器的回调函数
- ...
JS是执行在渲染主线程上的,所以js是单线程的,为什么不能设计成多线程? 尽管可以设计多线程,但JavaScript的单线程模型主要是为了保持简单性和避免复杂的并发问题,如竞态条件和死锁
解决很忙冲突问题,是排队
渲染主线程 -> 任务 、 任务完成后从消息列表中去拿 事件循环、消息循环(message queue) 任务 、 任务、任务(其他线程)、不同线程监听的任务拿来排队
具体逻辑
- 最开始的时候、浏览器的渲染主线程会进入无限循环(for(;;){...})
- 每次循环检查消息队列中是否存在任务,有就执行,没有就进入休眠
- 其他所有线程,可以随时向消息队列添加任务,休眠状态下会唤醒继续循环拿任务
这样就可以让每个任务有条不紊的进行了,这个过程叫事件循环(消息循环)
2-1、异步
代码执行中,会遇到一些无法立即执行、比如记时 setTimeout、网络通讯完成后需要执行XHR、用户操作后需要执行的任务 addEventListener
如果让主线程等待就会导致长期 阻塞,导致浏览器卡死,主线程非常重要要负责页面渲染,无论如何不能阻塞主线程
计时线程调用操作系统的方法计时结束通知主线程执行
面试题:如何理解 JS 异步,对异步的解释? JS 是一门单线程的语言,这是因为它运行在浏览器的主线程中,而主线程只有一个,而渲染主线程承担着诸多的工作,渲染页面,执行 JS 都在其中 如果使用同步的方式,极有可能导致主线程产生阻塞,从而导致消息队列中的很多其他任务无法得到执行,这样一来。一方面导主线程白白消耗时间,另一方面导致页面无法及时更新,给用户造成卡死现象 所以浏览器采用异步的方式来避免、具体做法是当某些任务发生时,比如计时器、网络、事件监听、主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码,当其他线程完成时,将事先传递的回调函数包装成任务,加入消息队列的末尾排队,等待主线程调度执行 在这种异步模式下,浏览器永不阻塞,从而最大限度的保证了单线程的流程运行
JS 为何会阻塞渲染: 因为都在一个线程上
2-2、任务优先级
随着浏览器越来越复杂,w3c 不再使用宏队列的说法
最新解释
- w3c 同一个类型的任务必须在一个队列,不同类型可以分属不同队列,浏览器可以根据实际情况从不同队列取任务执行
- 微队列不为空的时候,微队列中的任务优先于所有其他任务执行
延时队列、交互队列、微队列 Promise、MutaionObserver
Promise.resolve().then(()=>{ xxx })
注意: 微队列是优先级最高的,哪怕本次微任务中还有新的微任务生成,会一直清空微任务队列
function a(){
console.log(1)
Promise.resolve().then(function(){
console.log(2)
})
}
setTimeout(function(){
console.log(3)
},0)
Promise.resolve().then(a)
console.log(5)
输出 5、1、2、3
面试题:阐述 JS 事件循环
事件循环又叫做消息循环,是浏览器渲染主线程的工作方式 在 Chrome 的源码中,它开启了一个不会结束的 for 循环, 每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入任务队列末尾即可 过去把消息队列简单分为宏队列和微队列,这种说法目前无法满足复杂的浏览器环境, 取而代之的是一种更灵活多变的处理方式 根据 W3C 的官方解释,每一个任务有不同的类型,同类型的任务必须在同一个队列,不同任务可以属于不同的队列,不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务,但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行
面试题:JS中的计时器可以做到精准计时吗,为什么
不行 1、计算机硬件没有原子钟,无法精确 2、操作系统的计时函数本身有少量偏差 3、按照 W3C 标准吗浏览器实现计时器,超过5层开始会带有4ms的偏差 4、受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此带来了偏差
单线程是异步产生的原因、事件循环是异步的实现方式