all-of-javascript
all-of-javascript copied to clipboard
读浏览器的工作原理一文有感而瞎JB写
读浏览器的工作原理一文有感而瞎JB写
参考:https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/ 上文虽是 2011 年的文章,还是啃一下,温故知新。如果有不对之处,还请指正。
浏览器的主要组件
- 用户界面:除去你请求的资源展示之外的部分,包括地址栏、前进后端按钮等等
- 浏览器引擎:用户界面与内核之间的通信中间层,传递指令
- 呈现引擎:又称浏览器内核 (blink, webkit, ...),负责呈现请求的内容,比如一个 html 页面
- 网络:用于网络调用,比如 HTTP 请求
- 用户界面后端:绘制基本的窗口组件
- javascript 解释器:解释型语言需要一个解释器来解析、执行(V8...)
- 数据存储:持久化层,浏览器需要在硬盘上保存各种数据,例如 Cookie
Q1: 每个标签页都对应一个呈现引擎的实例,每个标签页都是单独的进程?
A:
可以自行测试,在打开新标签页进行操作的时候确实增加了进程数目,操作系统的每个进程都有着自己的控制模块和内存中特定存储区域。 多进程浏览器是为了利用进程的隔离性创建了一个安全的沙箱环境。使得一个页面的奔溃不会影响另一个页面,一个页面的恶意攻击代码也不会取到别的页面的敏感信息。 吃内存的毛病可以通过强大的内存释放算法逻辑弥补嘛。
主流程
基本流程:

比如一个网站的访问,首先是 资源的拉取 过程,也就是下载相关的 js、css、静态资源等等,在同域名下 chrome 支持的最大下载并发数是 6 个,多余的则要在队列中等待。
Q2:如何最大化的加快资源的拉取速度呢
A:
参考:网页性能优化实战
- 把资源放在 n 个不同的域名下,最大利用这一并发机制(貌似 n 最大是 10, 所以是可以达到 n * 6 个并发的)
- 异步加载,其实也是并发的一种形式,方式可以是使用 async 属性,或者函数动态引入等
- 延迟加载, defer
- 预加载的技术,比如 preload, prefetch, preconnect, dns-prefetch,这些都是间接的加快资源的加载
- 资源的压缩、合并、CDN、缓存、按需加载等日常基本优化操作
- 你来补充
接着是第二个步骤:解析并构建render-tree,包含 DOM Tree 的生成、CSSOM 的生成,以及最终 Render Tree 的生成。

参考:http://imweb.io/topic/56841c864c44bcc56092e3fa
然后是 布局(layout) 和 绘制(paint),最终一个页面就出现在了用户面前。
有点乱,我们梳理一下
从性能(耗时)角度,浏览器请求、加载、渲染一个页面大致有以下步骤:
- DNS LookUp
- TCP Connection
- Http Request and Response
- Server Response
- Client-Side Rendering
我们只关注 第 5 个步骤 其主要流程:
- Html Parser 解析 HTML,构建 DOM 树
- Css Parser 解析 CSS,构建 CSSOM
- 将 DOM Tree 和 CSSOM 整合生成 Render Tree -- 包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。
- CPU 登场,进行布局(layout、reflow), 确定节点的在屏幕的几何坐标
- GPU 登场,绘图(render/paint),将各个节点绘制到屏幕上
上面是一个渐进式的过程,不一定一次性顺序执行,内核不一定会等到 html文档解析完了才去构建 render-tree 或者布局,一切皆是为了快!快!快!(更好的用户体验) 当 DOM 或者 CSSOM 被修改的时候,以上的过程需要部分或者全部重新执行一次,这就是回流(这里就是一个性能优化的关键点!) 还是那句名言:回流一定会引起重绘,反之不一定。 参考:回流与重绘
看图说话:

1. 解析
html 和 css 都需要经过解析器解析成对应的节点树结构,所以我们首先知道什么是解析? 怎么去解析?
解析文档是指将文档转化成为有意义的结构,也就是可让代码理解和使用的结构。解析得到的结果通常是代表了文档结构的节点树,它称作解析树或者语法树。
解析的过程可以分成两个子过程:词法分析和语法分析
简单的说解析的过程就是基于有限自动状态机最终实现一个 Token 化输出,然后语法分析器基于语法规则或者定义把这些 Token 串起来构建需要的抽象语法树(AST) 。

浏览器解析的过程中就是不断迭代,从词法分析器请求一个 Token 之后然后就去语法分析器匹配相应的规则,匹配到了就把该节点加到语法树,没有任何对应的语法规则就抛出异常,说明该文档无效,语法错误。
解析过后的过程就是将语法树翻译成机器代码或者是中间代码(编译器登场,而后的事情交给编译后端啦~)。更多内容,有兴趣的同学可以去学习编译原理相关的知识。

1.1 html 解析
通过对解析过程的了解,我们可以获悉如果语法规则越宽泛|宽松,那么对应的解析算法就会越复杂,不幸的是,html 就是这样的!
所以不能用现成的,浏览器就自己基于解析规范实现了解析算法:分为标记化和树构建两部分,与词法分析语法分析对应。
还是基于状态机的,巴拉巴拉一堆状态的切换、标记,相当的复杂,有兴趣的童鞋可以自行去了解。
这里有两个有意思的地方:
- 在解析完成之后,浏览器会将文档标注为交互状态,然后开始去解析那些处于'deferred'模式的脚本,这也就是常在各种性能优化博文中看到的 defer!不会阻塞渲染的原因。然后文档状态标注为完成,触发加载事件
- 各大厂商在浏览器上你争我夺,各自发展的情况下,对 html 解析过程中更宽泛的容错处理确是出奇的一致
1.2 css 解析
早期的 chrome 浏览器通过 webkit 中 flex、bison 解析生成器生成了 CSS 解析器。基本大同小异,不再赘述。
Q3:css加载会阻塞页面渲染吗?
A:
无论是外链 CSS 还是内联 CSS 都会阻塞 DOM 渲染(Rendering),然而 DOM 解析(Parsing)会正常进行,这样设计的好处很好理解,比如避免 FOUC,避免回流等 不论是内联还是外链 JavaScript 都会阻塞后续 DOM 解析(Parsing),DOMContentLoaded 事件会被延迟,后续的 DOM 渲染(Rendering)也会被阻塞,这个也很好理解,毕竟 js 是可以改变 DOM 结构的,所以等 js 先加载完再解析。
Q4:css会阻塞js的加载或者执行吗?会延迟DOMContentLoaded事件吗?
A:
JavaScript 是可能改变 CSSOM 结构的,所以必须等到 CSSOM 构建好了再解析,避免没必要的重绘。所以 CSS 是会阻塞后边的 JS 执行的。而且 CSS 还会阻塞图片资源的解码! 对于 DOMContentLoaded: 首先记住,DOMContentLoaded 事件本身不会等待 CSS 文件、图片、iframe 加载完成。 但是它的触发时机有点意思了,加载完页面,解析完所有标签(不包括执行CSS和JS),并如规范中所说的设置 interactive 和执行每个静态的script标签中的JS,然后触发。 也就是说其执行时机依赖于执行完 JS ,所以我们可以推出:
- 如果页面中没有 script 标签,DOMContentLoaded 事件并没有等待 CSS 文件、图片加载等完成
- 如果页面中静态的写有 script 标签,DOMContentLoaded 事件需要等待 JS 执行完才触发
- 如果页面中静态的写有 script 标签,还有 link,那么 script 标签中的 JS 需要等待位于其前面的 CSS 的加载完成,而 JS 又延迟 DOMContentLoaded 事件触发。
现代浏览器会并发的预加载CSS, JS,也就是一开始就并发的请求这些资源,但是,执行CSS和JS的顺序还是按原来的依赖顺序(JS的执行要等待位于其前面的CSS和JS加载、执行完)串行执行。先加载完成的资源,如果其依赖还没加载、执行完,就只能等着
1.3 render tree 构建
神图镇楼:

构建这么一颗渲染树做了什么:
- 从 DOM 树的根节点开始遍历每个可见节点 --> 不可见节点,比如脚本标记、css 隐藏的节点
- 对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们
- 发射可见节点,连同其内容和计算的样式
参考:官方文档
2. 布局和渲染
有了渲染树,什么都好办了哈。
之前提到了布局是 CPU 完成的,绘制是 GPU 完成的。因为浏览器会将各层的信息发送给GPU(GPU进程:最多一个,用于3D绘制等),GPU会将各层合成(composite),显示在屏幕上。
参考:浏览器渲染流程
这里主要关键点是 回流 重绘 硬件加速
Q5:回流重绘的优化?
A: 首先明确一点:回流的代价比重绘更高。 现代浏览器会对频繁的回流或重绘操作进行优化: 浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。 网上有很多关于回流、重绘优化的办法,比如这里, 这里
Q6:硬件加速与性能优化?
A:
CSS3 硬件加速又叫做 GPU 加速,是利用 GPU 进行渲染,减少 CPU 操作的一种优化方案。由于 GPU 中的 transform 等 CSS 属性不会触发 repaint,所以能大大提高网页的性能。 阿里有一个很棒的文章 以及这个