深入浏览器的渲染原理

140 阅读6分钟

1. 输入URL网页的请求显示的过程

  • 首先通过DNS服务器进行域名解析

  • 解析出对应的IP地址,然后从ip地址对应的主机发送http请求 获取对应的静态资源,默认情况服务器会返回index.html文件

    • 浏览器内核开始解析HTML
    • 首先会解析对应的html 生成DOM Tree
    • 解析过程中,如果遇到css的link标签 则会下载对应的css文件
      • 下载css文件和生成DOM树是同时进行
    • 下载完对应的css文件后会进行css解析 生成CSSOM( CSS object model,CSS对象模型)
    • 当DOM Tree和CSS Tree都解析完成之后 会进行合并用来生成Render Tree(渲染树)
  • 初步生成的渲染树会显示节点以及部分样式 但是并不表示每个节点的尺寸 位置信息 于是进行Layout(布局)来生成渲染树中节点的宽度 高度位置信息

  • 经过Layout之后 浏览器内核将布局时的每个frame转屏幕上的每个像素点 将每个节点绘制到屏幕上

    网页渲染.jpg 注意: 第一次确定节点的大小位置称之为布局(Layout),之后对节点大小位置改变后的重新计算称之为回流

2. 网页的渲染过程

  • HTML解析 -> DOM Tree

  • CSS解析 -> CSSOM Tree

  • 生成 Render Tree(渲染树)

    • 当有了DOM Tree和 CSSOM Tree后,就可以两个结合来构建Render Tree了,显示哪些节点以及其他样式,但是不表示每个节点的尺寸、位置等信息

      • link元素不会阻塞DOM Tree的构建过程,但是会阻塞Render Tree的构建过程,因为Render Tree在构建时,需要对应的CSSOM Tree;
    • Render Tree和DOM Tree并不是一一对应的关系,比如对于display为none的元素,压根不会出现在render tree中

    image.png

  • 进行layout布局

    • 布局是确定呈现树中所有节点的宽度、高度和位置信息
  • 进行paint绘制

    • 在绘制阶段,浏览器将布局阶段计算的每个frame转为屏幕上实际的像素点,包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素(比如img)

    image.png

3. 回流和重绘

3.1 回流

  • 理解回流reflow:(也称为重排)

    • 第一次确定节点的大小和位置,称之为布局(layout),之后对节点的大小、位置修改重新计算称之为回流。
  • 什么情况下会引起引起回流?

    • DOM结构发生改变(添加新的节点或者移除节点)

    • 改变了布局(修改了width、height、padding、font-size等值)

    • 窗口resize(修改了窗口的尺寸等)

    • 调用getComputedStyle方法获取尺寸、位置信息

3.2 重绘

  • 理解重绘repaint:

    • 第一次渲染内容称之为绘制(paint),之后重新渲染称之为重绘。
  • 什么情况下会引起重绘?

    • 修改背景色、文字颜色、边框颜色、样式等

3.3 总结

  • 回流一定会引起重绘,所以回流很消耗性能,应该尽量避免回流
  • 如何避免回流?
    1. 修改样式时尽量一次性修改

      • 比如通过cssText修改,比如通过添加class修改
    2. 尽量避免频繁的操作DOM

      • 可以在一个DocumentFragment或者父元素中,将要操作的DOM操作完成,再一次性的操作
    3. 尽量避免通过getComputedStyle获取尺寸、位置等信息

    4. 对某些元素使用position的absolute或者fixed

      • 并不是不会引起回流,而是开销相对较小,不会对其他元素造成影响

4. 合成composite

绘制的过程,可以将布局后的元素绘制到多个合成图层中,是浏览器的一种优化手段

  • 默认情况下,标准流中的内容都是被绘制在同一个图层中
  • 某些特殊的CSS属性, 会生成新的合成图层(每个合成层都是单独渲染的):
    • 3D transforms
    • video、canvas、iframe
    • opacity 动画转换时
    • position: fixed
    • will-change:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化
    • animation 或 transition 设置了opacity、transform
  • 有动画时尽量使用transform或者opacity,性能更高
  • 注意:分层确实可以提高性能,但是它以内存管理为代价,因此不应作为 web 性能优化策略的一部分过度使用

5. defer和async

5.1 script元素和页面解析的关系

  • 页面渲染时的JavaScript

    • 浏览器在解析HTML的过程中,遇到了script元素是不能继续构建DOM树的,它会停止继续构建,首先下载JavaScript代码,并且执行JavaScript的脚本,只有等到JavaScript脚本执行结束后,才会继续解析HTML,构建DOM树
    • 这是因为JavaScript的作用之一就是操作DOM,并且可以修改DOM,如果等到DOM树构建完成并且渲染再执行JavaScript,会造成严重的回流和重绘,影响页面的性能。所以会在遇到script元素时,优先下载和执行JavaScript代码,再继续构建DOM树。
  • 这个带来新的问题,特别是现代页面开发中:

    • 在目前的开发模式中(比如Vue、React),脚本往往比HTML页面更“重”,处理时间需要更长
    • 会造成页面的解析阻塞,在脚本下载、执行完成之前,用户在界面上什么都看不到
  • 为了解决这个问题,script元素给我们提供了两个属性(attribute):deferasync

5.2 defer

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

    • 脚本会由浏览器来进行下载,但是不会阻塞DOM Tree的构建过程

    • 如果脚本提前下载好了,它会等待DOM Tree构建完成,在DOMContentLoaded事件之前先执行defer中的代码

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

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

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

5.3 async

  • async是让一个脚本完全独立的:

    • 浏览器不会因 async 脚本而阻塞(与 defer 类似)

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

    • async不能保证在DOMContentLoaded之前或者之后执行

5.4 defer 和 async的区别

  • defer通常用于需要在文档解析后操作DOM的JavaScript代码,并且对多个script文件有顺序要求的

  • async通常用于独立的脚本,对其他脚本,甚至DOM没有依赖的