事件循环Event Loop
事件循环应该是浏览器的机制,但浏览器会执行JS,所以它会体现在JS上
浏览器的进程模型
进程
进程是应用程序执行的实例,程序运行时需要诸如一片内存空间等的资源
每个运行的应用程序至少具有一个进程,进程之间相互独立,只有在双方同意的情况下才能进行通信
Process (computing) - Wikipedia
线程
通常来说,线程是被进程独立管理的一个组件,被同一个进程管理的线程可以共享同一片内存
一个进程至少拥有一个线程,所以在进程开启后,其会自动创建一个线程运行代码,这个线程就是主线程,主线程结束意味着整个进程结束
一个进程可以拥有多个线程,当应用程序需要(宏观上)同时执行多块代码时,就会开启更多的线程
所以线程是打工人(误)
Thread (computing) - Wikipedia
浏览器的进程和线程
浏览器是一个多进程多线程的应用程序,其内部工作很复杂
为了避免相互影响、减少连环崩溃的几率,当开启浏览器时,它会自动启动多个进程
浏览器进程:负责界面(不是页面哦)显示、用户交互、子进程(其他进程)管理等,会启动多个线程处理不同的任务,只有一个
网络进程:负责加载网络资源,会启动多个线程处理不同的网络任务
渲染进程
会启动一个渲染主线程负责执行HTML、CSS、JS三剑客代码
浏览器默认会为每个标签页开启一个新的渲染进程,以确保不同标签页间不相互影响
作为优化,将来可能会变为按站点分配进程
GPU渲染进程:主要负责3D渲染,不一定会用到
插件进程:主要负责加载第三方插件,不一定会用到
我这个不爱清理标签页的懒狗表示,这么多任务真是吓人XD
渲染主线程
渲染主线程是浏览器中最繁忙的线程,它处理的任务包括但不限于:
- 解析HTML
- 解析CSS
- 计算样式(单位换算、层叠计算等)
- 布局(元素的几何信息)
- 处理图层(z-index)
- 每秒绘制页面60次
- 执行全局JS代码
- 执行事件处理函数
- 执行计时器的回调函数
- 等等等等
渲染主线程可能面临各种各样的场景,例如
- 场景A:线程正在执行一个JS函数,突然用户点击了按钮
- 场景B:还是在执行一个函数,突然某个计时器时间到
- 场景C:浏览器进程通知“用户点击了按钮”,同时某个计时器时间到
任务太多了,渲染主线程该如何调度任务?它的答案是:任务队列
淦哦,我懒得再自己画了
最开始,渲染主线程会进入一个无限循环
每次循环都会检查消息队列中是否有任务存在
- 如果有,就取出第一个任务执行,执行完一个任务后进行下一次循环
- 如果没有,进入休眠状态
其他所有线程(包括其他进程的线程)可以随时向消息队列添加新任务
比如浏览器进程处理的用户交互,它自身不执行JS代码,但会把JS事件回调包装成一个任务推入任务队列
- 这个新任务会被添加到消息队列的末尾,等待执行
- 如果此时渲染主线程是休眠状态,则将其唤醒,以此继续循环、执行任务
这个过程就被称为事件循环(Event Loop)
概念
异步
代码执行过程中,会遇到一些无法立即处理的任务,比如
- 一段时间后才执行的任务,例如setTimeout、setInterval
- 网络通信完成后才执行的任务,例如XHR、fetch
- 用户操作后需要执行的任务,例如addEventListener
如果让渲染主线程只是干巴巴地傻等着这些任务的执行时机,就会导致主线程长期处于阻塞状态,让浏览器卡死
同步:按顺序执行,执行完一个执行下一个
但是渲染主线程还有很多重要的事要做,那么多任务等着,可不敢阻塞!所以浏览器使用异步解决这个问题
异步:彼此独立执行,等待事件的同时继续做别的事
页面卡顿
有这样一段代码
1 |
|
结果是,点击按钮三秒后标题才会改变
消息队列优先级
任务没有优先级,但消息队列本身是有优先级的
根据W3C标准:
- 每个任务都有一个任务类型,同一类型的任务必须属于同一队列、不同类型的任务可以分属不同队列(队列可处理不同类型的任务),在一次事件循环中,浏览器可根据实际情况从不同的队列中取出任务执行
- 浏览器必须准备一个微队列,其中的任务优先于所有其他任务的执行HTML Standard
而chrome浏览器中包含这些队列:
延时队列:存放计时器到时的回调任务,优先级【中】
交互队列:存放用户操作后的事件处理任务,优先级【高】
微队列:存放需要最快执行的任务,优先级【最高】
添加任务到微队列的主要方式是使用Promise或者MutationObserver
1
2// 其实就是立即把一个函数添加到微队列
Promise.resolve().then(fn)等等等等
以前的标准为两个消息队列:宏队列(普通通道)和微队列(VIP通道),但已不适用于现在日渐复杂的浏览器
1 | setTimeout(() => { |
答案
2
1
1 | setTimeout(() => { |
答案
(停顿一秒)
2
1
1 | setTimeout(() => { |
答案
3
2
1
1 | const a = ()=> { |
答案
5
4
3
1
2
1 | // 答案:4 1 2 3 |
答案
4
1
2
3
1 |
|
答案
添加延时任务
添加交互任务
begin…
执行交互任务
执行延时任务