虚拟DOM原理,性能分析(权衡的艺术)

79 阅读4分钟

虚拟DOM原理,性能分析

命令式和声明式

  • 命令式
    • 关注过程
    • 描述的是“做事的过程”,这符合我们的逻辑直觉
  • 声明式
    • 关注结果
    • 内部由命令式实现

性能与可维护性的权衡

声明式代码的性能不优于命令式代码的性能。

  • 性能对比
    • 把直接修改的性能消耗定义为 A,把找出差异的性能消耗定义为 B
    • 命令式代码的更新性能消耗 = A
    • 声明式代码的更新性能消耗 = B + A
  • 框架本身就是封装了命令式代码才实现了面向用户的声明式
  • 声明式代码的可维护性更强,在保持可维护性的同时让性能损失最小化

虚拟DOM的性能到底如何

虚拟 DOM,就是为了最小化找出差异的性能消耗

  • 声明式代码的更新性能消耗 = 找出差异的性能消耗+ 直接修改的性能消耗

innerHTML和虚拟DOM操作元素性能比较

    • innerHTML 操作
      • 字符串解析成 DOM 树,DOM 层面的计算(DOM 的运算要远比 JavaScript 层面的计算性能差)
      • HTML 字符串拼接的计算量 + innerHTML 的 DOM计算量
    • 虚拟DOM操作
      • 创建 JavaScript 对象,这个对象可以理解为真实 DOM 的描述
      • 递归地遍历虚拟 DOM 树并创建真实 DOM
      • 创建 JavaScript 对象的计算量 + 创建真实 DOM 的计算量

innerHTML和虚拟DOM更新页面时的性能比较

    • 使用 innerHTML
      • 重新构建 HTML 字符串
      • 重新设置 DOM 元素的 innerHTML 属性(只更改了一个文字,也要重新设置 innerHTML 属性)
      • 重新设置innerHTML 属性就等价于销毁所有旧的 DOM 元素,再全量创建新的 DOM 元素
    • 使用虚拟DOM
      • 重新创建JS对象(虚拟DOM树)
      • 对比新旧虚拟DOM
      • 找到变化的元素并更新

innerHTML操作与虚拟DOM性能消耗完整对比

    • 虚拟 DOM 在 JavaScript 层面的运算要比创建页面时多出一个 Diff 的性能消耗
    • Diff计算是是 JavaScript 层面的运算,所以不会产生数量级的差异
    • DOM 层面的运算
      • 虚拟 DOM 在更新页面时只会更新必要的元素
      • innerHTML需要全量更新
    • 虚拟无论页面多大,都只会更新变化的内容
    • innerHTML页面越大,就意味着更新时的性能消耗越大

综合对比

运行时和编译时

有没有办法做到,既声明式地描述UI,又具备原生 JavaScript 的性能

纯运行时

    • 提供一个 Render 函数
    • 为该函数提供一个树型结构的数据对象
    • Render 函数会根据该对象递归地将数据渲染成 DOM 元素
    • 用户使用过程
      • 实现 Render 函数
      • 实现 Render 函数
      • 使用Render函数
    • 不能支持用类似于 HTML 标签的方式描述树型结构的数据对象

运行时+编译时

    • 将HTML 标签编译成树型结构的数据对象
    • 分别调用 Compiler 函数和 Render 函数
    • 特点
      • 支持运行时,用户可以直接提供数据对象从而无须编译
      • 支持编译时,用户可以提供 HTML 字符串,我们将其编译为数据对象后再交给运行时处理
      • 运行时编译,代码运行的时候才开始编译,会产生一定的性能开销
      • 可以在构建的时候就执行 Compiler 程序将用户提供的内容编译好,极大提高性能

纯编译时

  • 将 HTML 字符串编译为命令式代码

总结

  • 命令式在理论上可以做到极致优化,但是用户要承受巨大的心智负担
  • 而声明式能够有效减轻用户的心智负担,但是性能上有一定的牺牲
  • 声明式的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗
  • 虚拟 DOM 的意义就在于使找出差异的性能消耗最小化
  • Vue.js 3 是一个编译时 + 运行时的框架,它在保持灵活性的基础上,还能够通过编译手段分析用户提供的内容,从而进一步提升更新性能