切换主题
现代浏览器基础认识
更新时间: 6/20/2020字数: 0 字
以 chrome 为例。

浏览器进程
Browser 进程 浏览器的主进程
作用:负责界面显示、用户交互、子进程管理,同时提供存储等功能。
渲染进程 也被称为浏览器内核
作用:负责页面的渲染,脚本执行,时间处理,网络请求等功能。默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
每个 tab 页会单独占用一个渲染进程。
tab 页内的
<iframe>
也会占用一个渲染进程;GPU 进程 与图形渲染有关
插件进程 运行一个浏览器插件,就有一个进程
作用:负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离
其他进程等 比如:储存进程、网络进程、音频进程等
渲染进程
重点关注浏览器的渲染进程。渲染进程包括以下线程:
GUI 渲染线程
解析 html 文档,生成 DOM 树与 CSS 树(css 树不会阻塞 dom 树的生成**)。当生成 DOM 树与 CSS 树之后,就根据这两棵树合并生成一个 render 树(**在生成 render 树的时候,如果有一方没有解析完毕就会等待解析完成。此时此刻是双方会互相阻塞**),然后将这个 render 树渲染到界面上。当页面触发回流或者重绘的时候,会再次执行此次操作。
JS 引擎线程和 GUI 渲染线程是互斥。
JS 引擎线程
执行 JS 代码。JS 引擎线程和 GUI 渲染线程是互斥,当一个执行的时候,另外一个就会被强制挂起。
当 JS 执行一个费时的代码时候,因为迟迟不能执行完毕,导致 GUI 渲染线程被挂起太久,就会导致页面看起来卡顿,事件响应变慢。解决办法,可以通过Web Worker解决。
事件触发线程
定时器线程
setInterval 与 setTimeout 所在的线程,定时任务并不是由 JS 引擎计时的,是由定时触发线程来计时的、计时完毕后,将回调函数放入到宏任务队列中
异步 http 请求线程
事件循环
js 引擎线程在解析 js 代码时,会将同步代码按顺序排在某个地方,即执行栈,然后依次执行里面的函数。当遇到异步任务时就交给其他线程处理。
当前执行栈所有同步代码执行完成后,会从一个队列中去取出 1 个已完成的==异步任务的回调==加入执行栈继续执行,如果再遇到异步任务时又交给其他线程,.....,如此循环往复。
而其他异步任务完成后,将回调放入任务队列中等待执行栈来取出后执行。

通过不断循环去任务队列中,取出异步回调来执行,这个过程就是事件循环。每一次循环就是一个事件周期或称为一次 tick。
宏任务和微任务
任务队列不只一个,根据任务的种类不同,可以分为微任务(micro task)队列和宏任务(macro task)队列。
事件循环的过程中,执行栈在同步代码执行完成后,优先检查微任务队列是否有任务需要执行。
注意:现在 MDN 文档上,没有宏任务的说法,只有微任务和任务。可以把任务理解成宏任务。
常见宏任务
- 整块 script 标签代码
- setTimeout()
- setInterval()
- 事件
常见微任务
- promise.then()、promise.catch()
- Async 函数
- new MutaionObserver()
- process.nextTick()
浏览器中定时器误差
事件循环中,总是先执行同步代码后,才会去任务队列中取出异步回调来执行。当执行 setTimeout 时,浏览器启动定时器线程去计时,计时结束后触发定时器事件将回调函数存入宏任务队列,等待 JS 主线程来取出执行。
如果这时主线程还在执行同步任务的过程中,那么此时的宏任务就只有先挂起,这就造成了计时器不准确的问题。同步代码耗时越长,计时器的误差就越大。
不仅同步代码,由于微任务会优先执行,所以微任务也会影响计时,假设同步代码中有一个死循环或者微任务中递归不断在启动其他微任务,那么宏任务里面的代码可能永远得不到执行。
视图渲染
微任务队列执行完成后,也就是一次事件循环结束后,浏览器会执行视图渲染,当然这里会有浏览器的优化,可能会合并多次事件循环的结果做一次视图重绘,因此视图更新是在事件循环之后,所以并不是每一次操作 Dom 都一定会立马刷新视图。
视图重绘之前会先执行 requestAnimationFrame 回调,那么对于 requestAnimationFrame 是微任务还是宏任务是有争议的,在这里看来,它应该既不属于微任务,也不属于宏任务。