一些浏览器事件循环的理解
事件循环
这一行为运作在浏览器的渲染主线程上,它会一直遍历消息队列
任务调度 / 消息队列
主线程的任务调度依靠消息队列解决
在主线程执行全局 JS 时,若全局 JS 中需要进行异步操作时(例如 Promise.resolve().then(fn)
,setTimeout(fn,0)
,addEventListener('click‘,fn)
) 他们将会在别的线程中处理完后将要做的回调函数添加至队列中
而后在主线程的轮询队列中被调用执行
异步
由于 JS 是单线程因此需要异步来解决同步的阻塞问题,而 JS 是单线程的原因是它运行在渲染主线程中
而渲染主线程不单单只为 JS 工作,也承担渲染工作
若采用同步工作方式将会导致主线程阻塞,从而影响其他工作,且无意义的 IO 会导致时间被白白浪费,并且还会导致网页卡死
因此采用异步,异步任务发生时将其转交给其他线程处理,而在其他线程处理完后将回调函数包装成任务重新加入到消息队列中等待主线程调用
而这也是导致异步污染的原因,因为倘若异步无需被等待那么其整个调用链可能都不需要异步这个结果,那么将不会有异步的问题,而一旦其调用链路中有值需要等待其回调结果,那么其他函数的返回理应都依靠其结果从而导致了异步污染
主线程的阻塞
任意陷入死循环的同步代码都将会阻塞渲染主线程,为何?
- 消息队列中,渲染行为是属于任务之一,而执行 JS 也是任务之一,当执行 JS 时,修改了 DOM 将会触发渲染行为,但是触发渲染行为只是将绘制任务添加至队列中,只有当 JS 结束后才会执行绘制任务,因此不合理的同步 JS 会导致主线程卡死
那么在阻塞渲染主线程时触发了其他任务会发生什么?
- 若在阻塞时间内,计时器到时、网络请求完成、文件读取完成、发生了新的触发事件、改变了窗口大小等其他事件,他们的任务将会加入消息队列中,直到当前任务完成后轮询处理
优先级
任务没有优先级,但是消息队列有优先级
目前貌似并无宏队列这类说法,而是由浏览器自己实现其队列
而定义中最高优先级为微队列 (Micro Task Queue)(并且也仅有这一队列要求必须实现),而在 Chrome 的实现中拥有 微队列(最高优先级) 、交互队列(高优先级) 、延时队列(中优先级) 三种队列
而同类型的任务必须在同队列,不同类型的任务可以处于不同队列
而
Promise
、MutationObserver
的回调函数将会被排入微队列中若要手动调用微队列可以使用
queueMicrotaks(fn)
, 若是使用立即返回的 Promise (即Promise.resolve().then(fn)
来进行的话其回调抛出的异常将会表现为被拒绝的 Promise,即异常被吞了,但是在运行时仍会报错,并且 Promise 的创建和销毁在时间和内存方面都需要额外开销)