浏览器基本原理

浏览器输入URL到页面展示过程

查看
  1. 输入URL并解析

    • 检查输入内容是否为关键字(自动补全或跳转搜索引擎或进入浏览器设置页面)
    • 对URL进行合法性检查和格式化
  2. 域名解析: 域名解析器(DNS: domain name system)将域名解析为IP地址, 浏览器通过IP地址访问服务器。

  3. 建立TCP连接(三次握手), 确保客户端与服务端双向通信能力。

  4. 发起HTTP/HTTPS请求。

  5. 服务器处理请求并返回响应。

  6. 浏览器解析与渲染。

  7. 加载动态资源与交互。

  8. 连接终止: TCP四次挥手。

浏览器渲染页面的的流程

查看

从服务器获取到资源后

  1. 创建DOM树: 解析HTML标签, 创建DOM树。

  2. 创建CSSOM树: 解析CSS样式, 创建CSSOM树。

    1. 当解析过程中发现存在link标签引用CSS文件时, 浏览器会向服务器请求CSS文件。
    2. 对外部CSS资源的下载解析会交给其他进程处理, 并不会阻塞DOM树的解析。
  3. 生成渲染树: 将DOM树和CSSOM树合并, 生成渲染树(rander tree)。

    1. 在此过程中, 渲染树的并不会存在每个节点的尺寸和位置信息。
    2. 对于渲染树, 可能存在等待CSSOM树解析完毕倒是渲染树生成的不及时的情况。针对这一点, 浏览器可能会对其进行一些优化操作。
  4. 布局(layout): 计算渲染树中每个节点的几何信息, 如宽高、位置等。

  5. 绘制(paint): 生成绘制指令列表,描述元素的绘制顺序和方式。

    1. 绘制是逻辑操作,不直接操作像素。
  6. 分块:既将页面或合成层划分成多个小块,进行独立光栅化和更新。

    1. 分块不是浏览器必须的,而是一种优化手段。
    2. 通过分块可以实现仅更新视口可见区域或既将进入视口的块。
    3. 由于可能存在多个合成层,所以可以将多个合成层划分的小块交予其他线程处理。
    4. 由于非可见区域的块可以延迟处理或降级质量,节省了内存。
  7. 光栅化:将渲染树中的矢量图形转为屏幕上的像素矩阵。

    1. 光栅化这一块是浏览器必须的。
    2. 光栅化可以调用GPU加速页面的绘制。
  8. 合成:将光栅化期间的像素矩阵交由合成线程进行合并。

  9. 显示: 画到屏幕上。

回流(reflow)和重绘(repaint)

查看

回流(reflow)

  • 第一次确定节点的大小和位置, 称为布局。

  • 之后对节点的大小和位置的修改, 称为回流(重排)。

回流(reflow)的触发方式

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

  • 改变布局(修改元素的宽度、高度、位置、边框、margin、padding、字体大小等)

  • 修改了窗口尺寸(resize)

  • 查询元素的尺寸和位置信息(offsetWidth、offsetHeight、offsetLeft、offsetTopget、getComputedStyle等)

重绘(repaint)

  • 第一次渲染称之为绘制(paint)。

  • 之后重新渲染, 称为重绘。

重绘(repaint)的触发方式

  • 修改了元素的背景色、边框颜色、字体颜色等。

回流一定会导致重绘

回流是一件很耗性能的事情, 所以尽量避免。

建议:

  • 修改样式时尽量一次性修改。

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

    • 比如通过DocumentFragment批量操作DOM或在父元素中将要操作的DOM操作完成,再一次性的操作。
  • 尽量避免通过getComputedStyle获取尺寸位置等信息。

  • 对某些元素使用position的absolute或fixed。

    • 其同样会造成回流, 但开销相对较小, 不会对其他元素产生影响。

script标签的defer和async属性

查看

defer

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

    • 脚本由浏览器来进行下载, 但不会阻塞DOM Tree的构建过程。
    • 如果脚本提前下载好, 他会等待DOM Tree的构建完成, 在DOMContentLoaded事件之前执行。
    • 所以DOMContentLoaded总是会等待defer中的代码先执行完成。
  • 对于多个defer的脚本, 浏览器会按照脚本的先后顺序执行。

  • 由于设置defer后, 并不会阻塞DOM树的构建, 并且会交与浏览器对其下载, 所以推荐将设置defer的脚本放在head中, 减少其因下载损耗的事件, 一定程度上可以提高页面的性能。

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

async

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

    • 浏览器不会因async脚本而阻塞。
    • async脚本不能保证顺序, 它是独立下载, 独立运行, 不会等待其他脚本。
    • async不能保证在DOMContentLoaded之前或之后执行。

defer和async的使用场景

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

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