事件循坏
浏览器的进程模型
进程
程序运行需要自己的专属的内存空间,这个空间就叫做进程,举个例子,chrome浏览器开多个窗口,就开了多个进程,默认情况下,每个进程互不影响。(chrome官网说将来可能会有改变 为 网站开始起了淘宝首页,然后有开启了一个淘宝子页面,把他们归结到一个进程)
线程
进程有了,就需要加载页面,然后代码也是在里面运行,这个过程就是线程,⼀个进程⾄少有⼀个线程,所以在进程开启后会⾃动创建⼀个线程来运⾏代码,该线程称之为主线程,如果程序需要执行多块代码,主线程就会启动更多的线程来执行这个操作
浏览器主要的进程有:
1:浏览器进程
主要负责界面显示,用户交互,子进程的管理等,浏览器进程内部可以启动多个线程来处理多个任务
2:网络进程
负责加载网络资源,网络进程内部也会启动多个线程来处理多个网络任务
3:渲染线程
渲染线程启动后,首先会开启一个渲染的主线程,然后主线程主要会负责执行HTML、CSS、JS代码,渲染线程也是前端需要知道的重要线程
渲染主线程的流程
渲染主线程是浏览器最繁忙的线程
- 解析HTML
- 解析CSS
- 计算样式
- 生成布局
- 处理图层
- 在屏幕刷新率的情况(60赫兹)每秒把页面画60次
- 执行全局的JS代码
- 执行事件处理函数
- 执行计时器的回调函数
- 等等
要处理这么多任务,主线程是如何去调度任务的,渲染线程用 排队 来解决这个问题
1:在最开始的时候,在 Chrome 的源码中,它开启⼀个不会结束的 for 循环, 渲染主线程会不断去执行
2:每一次执行会检查消息队列里面是否有任务存在,如过有,就取出第一个任务执行,执行完第一个后进去下一次循环执行,如果没有任务了,就会进入失眠状态。
3:其他所有进程也会随时向消息队列里面添加任务,新任务会加载消息队列的末尾,如果主线程处理休眠状态下,就会被唤醒以继续执行2的操作
整个过程被称为事件循环(在chrome内部被称为消息循环)
这样流程下来就可以解释JS的异步问题了
代码在执行过程中,会遇到一些无法处理的任务,比如:
- 计时完成后需要执行的任务 (setTimeout、setInterval)
- 网络通信完成后需要执行的任务 -- (XHR、Fetch、Axios)
- 用户操作后需要执行的任务 -- (addEventListener)
如果浏览器让渲染主线程等待致谢任务的时机到达的时候,就会导致主线程长期处于阻塞状态,从而导致浏览器卡死,渲染主线程承担极其重要的工作,所以无论如何都不能阻塞,从而浏览器用异步来解决这个问题。
在点击事件的时候页面进行了重新渲染,需要重新绘制,这时候就产生了一个绘制任务,浏览器会先把这个任务放在消息队列里面,等主线程执行完之后才会执行。
消息队列也有优先级的存在 根据W3C的最新解释:
- 每个任务都有一个任务类型,同一个类型的任务必须在一个队列里,不同类型的任务可以分属于不同的队列,在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。
- 浏览器里面还有一个微队列,微队列的任务优先于所有其他任务执行,比如 promise.then().catch().finally(),
随着浏览器的复杂程度,W3C不再使用宏队列的说法,而是进行的拆分 在目前chrome的实现中,至少包含了下面的队列
- 微队列:promise.then().catch().finally()、MutationObserver等 -- 优先级最高
- 交互队列:放⽤户操作后产⽣的事件处理任务 -- 优先级高
- 延时队列: 放计时器到达后的回调任务 -- 优先级中
setTimeout的误区
首先计时器是由浏览器去执行的,JS的计时器最终是调用操作系统的函数,操作系统本来就有少量的误差,浏览器实现计时器时候,如果嵌套超过5层,则会带来4毫秒的最少时间,往后嵌套都会带来4毫秒的最少时间。