梳理浏览器的运行原理

164 阅读7分钟

1. 浏览器的内核

  • Webkit -> Blink: Google Chrome Edge
  • Trident(三叉戟):百度、360、IE、搜狗、UC
  • Gecko(壁虎):Firefox
  • Presto(急板乐曲) -> Blisk: Opera

2. 渲染页面的详细流程

image.png

2.1 渲染前的操作

一般,我们输入一个网页的域名,经过 DNS 域名解析,查找到对应的 IP 地址,然后再到对应的服务器上查找 index.html

2.2 渲染过程

  • 1) HTML 经过HTML解析器会被转化成对应的 DOM 树(JS 可能会对节点做一些额外的操作);

image.png

image.png

  • 2) 在解析 HTML 的过程中遇到 CSS 样式,那么会由浏览器负责下载对应的 CSS 文件;浏览器下载完 CSS 文件后,就会对 CSS 文件进行解析,解析出对应的规则树,可以称之为 CSSOM(CSS Object Module);
    • 下载 CSS 文件是不会影响 DOM 树的解析

image.png

  • 3) 当有了 DOM Tree 和 CSSOM Tree 后,就可以结合形成渲染树 (Render Tree) 了。
    • css link 元素不会阻塞 DOM Tree 的构建过程,但是会阻塞 Render Tree 的构建过程
      • 这是因为 Render Tree 在构建时,需要对应的 CSSOM Tree
    • Render Tree 和 DOM Tree 并不是一一对应的关系,比如 display:none 的元素,不会出现在 Render Tree 中

image.png

  • 4) 布局(Layout)和绘制(Paint)
    • 在渲染树上运行布局以计算每个节点的几何体
      • 渲染树会表示显示哪些节点以及其他样式,但是不表示每个节点的尺寸、位置等信息;
      • 布局是确定呈现树中所有节点的宽度、高度和位置信息
    • 将每个节点绘制到屏幕上
      • 在绘制阶段,浏览器将布局阶段计算的每个frame转化为屏幕上实际的像素点
      • 包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素(比如 img);

image.png

  • 回流:reflow,也可以称之为重排。第一次确定节点的大小和位置叫做布局,之后对节点的大小、位置重新计算叫做回流。

回流的条件:

  1. 比如DOM结构发生改变(添加新的节点或者移除节点);
  2. 比如改变了布局(修改了width、height、padding、font-size等值);
  3. 比如窗口resize(修改了窗口的尺寸等);
  4. 比如调用getComputedStyle方法(上图中 Computed style 重新计算,导致后面操作都会发生改变)获取尺寸、位置信息;
  • 重绘:第一次渲染内容称之为绘制(paint);之后重新渲染称之为重绘。

重绘的条件: 修改背景色、文字颜色、边框颜色、样式等。

PS:回流一定会引起重绘,所以说回流是一件很消耗性能的事情。因此在开发过程中尽量避免发生回流:

  1. 修改样式时尽量一次性修改: 比如通过cssText修改,比如通过添加class修改
  2. 尽量避免频繁的操作DOM: 我们可以在一个DocumentFragment或者父元素中将要操作的DOM操作完成,再一次性的操作;
  3. 尽量避免通过getComputedStyle获取尺寸、位置等信息;
  4. 某些元素使用position的absolute或者fixed: 并不是不会引起回流,而是开销相对较小,不会对其他元素造成影响(这些元素已经脱标)。
  • 特殊解析-composite合成
    • 绘制的过程,可以将布局后的元素绘制到多个合成图层中。这是浏览器的一种优化手段。
    • 默认情况下,标准流中的内容都是被绘制在同一个图层(Layer)中的;
    • 而一些特殊的属性,会创建一个新的合成层(CompositingLayer),并且新的图层可以利用 GPU 来加速绘制。因为每个合成层都是单独渲染的;

    可以形成新图层的一些属性:

    1. 3D transforms
    2. video、canvas、iframe
    3. opacity 动画转换时
    4. position: fixed
    5. will-change:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化
    6. animation 或 transition 设置了opacity、transform
    • 分层确实可以提高性能,但是它以内存管理为代价,因此不应作为 web 性能优化策略的一部分过度使用。

3. script 元素和页面解析的关系

3.1 script 在页面中的解析

在浏览器解析HTML的过程中,遇到了script停止构建DOM树,首先下载 JavaScript 代码,并且执行 JavaScript 代码,只有等到 JavaScript 代码执行完成之后,才会继续解析 HTML,构建 DOM 树

  • 原因:

    • JavaScript 的作用之一就是操作 DOM,并且修改 DOM;
    • 等到DOM树构建完成并且渲染,再执行 JavaScript,会造成严重的回流和重绘,影响页面的性能;
    • 所以在遇到 script 元素时,优先下载和执行 JavaScript 代码,再继续构建 DOM 树。
  • 此方式会产生新的问题:

    • 目前的开发模式中(比如 Vue、React),脚本往往比 HTML 页面更“重”,处理时间更长
    • 会造成页面的解析阻塞,在脚本下载、执行完成之前,用户在界面上什么都看不到

3.2 解决问题的两个属性(attribute):defer&async

3.2.1 defer

  • defer 属性告诉浏览器不要等待脚本下载,而继续解析 HTML,构建 DOM Tree。

    • 脚本由浏览器来进行下载,但不会阻塞DOM Tree 的构建过程;
    • 脚本若提前下载好,它会等待 DOM Tree 构建完成在 DOMContentLoaded 事件之前先执行defer中的代码;
  • 所以 DOMContentLoaded 总会等待 defer 中的代码先执行

    image.png

  • 另外多个带defer脚本是可以保持正确的执行顺序的。

  • 从某种角度来说,defer可以提高页面的性能,并且推荐放到head元素中;

    • 注意:defer仅适用于外部脚本,对于script默认内容会被忽略。

3.2.2 async

  • async 特性与 defer 类似,它也能够让脚本不阻塞页面
  • async是让一个脚本完全独立执行的:
    • 浏览器不会因 async 脚本而阻塞(与 defer 类似);

    • async 脚本不能保证顺序,它是独立下载、独立运行,不会等待其他脚本;

    • async不会保证在 DOMContentLoaded 之前或之后执行;

      image.png

PS:

  • defer 通常用于需要在文档解析后操作 DOM 的JavaScript 代码,并且对多个 script 文件有顺序要求;
  • async 通常用于独立的脚本,对其他脚本,甚至 DOM 是没有依赖的。

4. V8引擎执行原理

image.png

V8 引擎会对 JavaScript 源代码进行解析,形成 抽象语法树(AST),然后经过 Ignition 操作转成字节码,而字节码可以跨平台运行,不过这种方式执行效率很低,每次执行都会转成字节码(byteCode)。于是乎,v8 会经过 TurboFan 操作转成优化的机器码(MachineCode),这样一来,可以减少许多不必要的操作,从而大大提升效率。

  • Parse 将 JavaScript 代码转化为 AST,这是因为解释器并不直接认识 JavaScript 代码
    • 如果函数没有被调用,那么是不会被转化成 AST 的;
    • Parse 的 V8 官方文档:v8.dev/blog/scanne…
  • Ignition 解释器,会将 AST 转化成 ByteCode
    • 同时会收集 TurboFan 优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算;
    • 如果函数只调用一次,Ignition 会解释执行 ByteCode
    • Ignition 的 V8 官方文档:v8.dev/blog/igniti…
  • TurboFan 编译器,可以将字节码编译为 CPU 可以直接执行的机器码
    • 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过 TurboFan 转化成优化的机器码,提高代码的执行性能
    • 但是,机器码实际上也会被还原为 ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如 sum 函数原来执行的是 number 类型,后来执行变成了 string 类型),之前优化的机器码并不能正确的处理运算,就会逆向的转化成字节码;
    • TurboFan 的 V8 官方文档:v8.dev/blog/turbof…