Vuejs设计与实现 —— 为什么需要虚拟 DOM

简介: Vuejs设计与实现 —— 为什么需要虚拟 DOM

image.png


前言

请思考下面的问题,你是否能够很好的回答出来:

  • 编程范式是什么?和 Vuejs 有什么关系?
  • 为什么需要虚拟 DOM?

如果你有一个明确的答案,那么也许你并不需要继续阅读下面的内容了,但是如果没有一个很明确的方向、思路去回答,那么建议你还是继续阅读!

编程范式

编程范式分为以下三种:

  • 命令式编程(Imperative)
  • 声明式编程(Declarative)
  • 函数式编程(Functional)

但视图层框架(如:Vue.js)通常使用的范式分为 命令式声明式,一个优秀的框架是在选择正确的范式的基础上,甚至是包含这两种范式优点的基础上进行实现的。

image.png

命令式框架

命令式框架 的特点就是 关注过程,而早年间流行的 jQuery 就是典型的命令式框架。

下面就通过实现一个简单需求来说明什么是命令式框架。

需求描述

  • 获取下面的 button 元素
  • 为其设置文本内容 "show message"
  • 为其添加对应的点击事件,点击按钮后弹出对应提示 "hello world"
<button id="btn"></button>
复制代码

实现需求

  • 原生 JavaScript 实现版本
var btn = document.querySelector('#btn') // 获取对应元素
btn.innerText = 'show message' // 设置文本内容
btn.addEventListenner('click', () => { // 绑定点击事件
    alert('hello world')
})
复制代码
  • jQuery 实现版本
$('#btn') // 获取对应元素
    .text('show message') // 设置文本内容
    .on('click', () => { // 绑定点击事件
      alert('hello world')
    })
复制代码

可以看到,代码实现过程需求描述 能够产生相互对应的关系,并且在代码实现中其实就是 "做事的过程",即不论你使用什么语言去实现,都可以按照特定的描述去实现功能。

声明式框架

声明式框架 的特点就是 关注结果,例如 Vuejs 就是声明式框架。

同样的,下面是结合 Vuejs 实现上述的功能需求的示例:

<button id="btn" @click="() => { alert('hello world') }">show message</button>
复制代码

可以看出,代码实现过程需求描述 并不是完全对应的,但是这段 Vuejs 的代码却能够实现需要的 结果

换句话说,是 Vuejs 将 实现过程 进行了封装,那么也就意味着其内部实现一定是 命令式 的,但是暴露给使用者却是 声明式 的。

Vuejs 为什么选择声明式

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

前面有提到,Vuejs 对外是声明式的,但内部实现还是命令式的,即最终的本质还是回归到了命令式,所以从这一点来看声明式代码的性能确实不优于命令式代码的性能。

例如,现在我们需要将前面按钮的文本内容改变成 "hello vue3"

命令式代码

通过执行以下代码后,修改立即生效:

btn.textContent = 'hello vue3' // 直接修改
复制代码

声明式代码

// 更新前
<button id="btn" @click="() => { alert('hello world') }">show message</button>
// 更新后
<button id="btn" @click="() => { alert('hello world') }">hello vue3</button>
复制代码

对于 Vuejs 来说,执行上面的代码后,它还需要找到修改前后的差异,并只更新变化的地方,但是对于最终的更新还是通过下面的代码:

btn.textContent = 'hello vue3' // 直接修改
复制代码

假设 A 为直接修改的性能消耗,B 为找出差异的性能消耗,则:

  • 命令式代码的更新性能消耗 = A
  • 声明式代码的更新性能消耗 = B + A即便是最理想的情况下(出差异的性能消耗为 0,即 B = 0),声明式代码才会和命令式代码的性能消耗相同,但也无法超越了。

选择声明式的原因

通过前面的内容得到的结论:在性能层面命令式代码相比于声明式代码是更好的选择

那既然如此,为什么 Vuejs 还是选择了声明式的设计方案呢?

原因就在于 声明式代码的可维护性更强

  • 命令式代码 在开发过程中,使用者要关注实现目标的整个过程,其中包括:DOM 元素的创建、更新、删除等,即代码体量一大,需要关注的内容就越多
  • 声明式代码 看上去更加直观,并且不需要关注具体的实现过程,因为这些具体实现都被封装在框架内部,即声明式代码的展示内容就是我们需要的 结果

在使用声明式提升可维护性的同时,虽然性能会有一定的损失,但是最好的结果就是:在保持可维护性的同时将性能损失最小化

Vuejs 为什么需要虚拟 DOM

原因

前面提到 声明式代码的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗,这就意味着只要可以实现 找出差异的性能消耗 的最小化,就可以在保证可维护性的同时,让 声明式代码 无限趋近于 命令式代码

有了前面的理论基础,你同样可以得到一个结论:虚拟 DOM 的更新技术 理论上 不可能比原生 JavaScript 操作 DOM 更高。因为大部分情况之下,我们是 很难写出绝对优化的命令式代码,尤其是当应用程序的规模很大的时候,如果你为了写出这样的代码而耗费了巨大的精力,那么最后的投入/产出的比例其实并不高。

小结虚拟 DOM 就是在保证使用声明式代码的同时,还能保证应用程式的性能下限,甚至是尽可能优化已达到命令式代码的消耗性能。

虚拟 DOM 的性能如何

上面提到的 原生 JavaScript 操作 DOM 性能更高,其中的 原生 JavaScript 操作 (如:createElement 等)并不包括 innerHTML ,因为它比较特殊。

虚拟 DOM 和 innerHTML 的性能比较

需求描述: 生成下如下的真实 DOM 结构:

<div>
 <span>hello world</span>
<div>
复制代码

实现需求:

以下都是伪代码实现

  • innerHTML 版本实现:
const html = `<div>
                <span>hello world</span>
              <div>`
document.body.innerHTML = html
复制代码
  • 虚拟 DOM 版本实现:
const app = {
    type:'div', 
    children:[
       {
         type: 'span',
         children: 'hello world',
       }
    ]
}
document.createElement(xxx)
document.body.append(xxx)
复制代码

创建阶段

  • innerHTML 创建页面的性能 = HTML 字符串拼接计算量 + innerHTML 的 DOM 计算量
  • 虚拟 DOM 创建页面的性能 = 创建 JavaScript 对象计算量(虚拟 DOM) + 创建真实 DOM 计算量

JavaScript 层面运算HTML 字符串拼接 和 创建 VNode 对象)上看,两者的差别并不大,因为都属于是 JS 的操作且都没有涉及 DOM。

DOM 层面运算(新建所有的 DOM 元素)上看,两者的差别也不大,因为在创建页面时,都需要创建所有的 DOM 元素。

更新阶段

  • innerHTML 创建页面的性能 = HTML 字符串拼接计算量 + innerHTML 的 DOM 计算量
  • 虚拟 DOM 创建页面的性能 = 创建 JavaScript 对象计算量(虚拟 DOM)+ Diff 计算量 + 必要的 DOM 更新计算量

JavaScript 层面运算HTML 字符串拼接 、 创建虚拟 DOM 对象和 Diff 对比)上看,innerHTML 还是一样的计算量,但是 虚拟DOM 多出来了一个 Diff 计算量。

DOM 层面运算(新建所有的 DOM 元素)上看,innerHTML 是先创建新的 html 字符串,然后销毁所有旧的 DOM 元素,再全量创建新的元素;而 虚拟DOM 会创建新的 虚拟 DOM 对象,然后通过 Diff 对比 新旧虚拟 DOM,找到变化的元素并更新它。

性能因素

  • 虚拟 DOM 只与数据变化量相关
  • innerHTML 与模板的大小相关

总结

由于 Vuejs 在设计层面选择了 声明式,但又由于 声明式 的性能并不优于 命令式,所以采用了 声明式 + 虚拟DOM 的方式达到更新时性能消耗的最小化,目的是为了在 可维护性强心智负担小 的前提下,同时保证性能消耗可以 趋近于 声明式的性能消耗。

下面这张图可以很好的从 心智负担性能可维护 层面表明为什么需要 虚拟 DOM

image.png

目录
相关文章
|
8天前
|
JavaScript 前端开发 编译器
说说你对虚拟 DOM 的理解?
说说你对虚拟 DOM 的理解?
21 0
|
8天前
|
JavaScript 前端开发 算法
MVVM模型,虚拟DOM和diff算法
1.MVVM是前端开发领域当中非常流行的开发思想。(一种架构模式)目前前端的大部分主流框架都实现了这个MVVM思想,例如Vue,React等2.虽然Vue没有完全遵循MVVM模型,但是Vue的设计也受到了它的启发。Vue框架基本上也是符合MVVM思想的 3.MVVM模型当中尝到了Model和View进行了分离,为什么要分离?
|
8天前
|
JavaScript 开发者 UED
虚拟DOM的原理
虚拟DOM的原理
32 1
|
8天前
|
JavaScript 前端开发 算法
js开发:请解释什么是虚拟DOM(virtual DOM),以及它在React中的应用。
虚拟DOM是React等前端框架的关键技术,它以轻量级JavaScript对象树形式抽象表示实际DOM。当状态改变,React不直接操作DOM,而是先构建新虚拟DOM树。通过高效diff算法比较新旧树,找到最小变更集,仅更新必要部分,提高DOM操作效率,降低性能损耗。虚拟DOM的抽象特性还支持跨平台应用,如React Native。总之,虚拟DOM优化了状态变化时的DOM更新,提升性能和用户体验。
33 0
|
8天前
|
前端开发 JavaScript 算法
深入理解前端框架:解析 React 的虚拟 DOM
虚拟 DOM 是 React 前端框架的核心概念之一。本文将深入探讨虚拟 DOM 的原理和应用,帮助读者更好地理解 React 框架的工作机制,并学习如何高效地使用虚拟 DOM 进行前端开发。
42 1
|
6天前
|
JavaScript 前端开发 算法
深入理解虚拟DOM:原理、优势与实践
深入理解虚拟DOM:原理、优势与实践
|
8天前
|
JavaScript 算法 前端开发
虚拟Dom
虚拟Dom
|
8天前
|
JavaScript 算法 前端开发
Vue的虚拟DOM:Vue虚拟DOM的工作原理
【4月更文挑战第24天】Vue的虚拟DOM提升渲染性能,通过创建JavaScript对象树(虚拟DOM树)来跟踪DOM变化。当状态改变,Vue用新的虚拟DOM树与旧树对比(diff算法),找到最小DOM操作集合来更新真实DOM。优化策略包括减少状态变化、使用key属性和简化组件结构。理解虚拟DOM工作原理有助于Vue的性能优化。
|
8天前
|
JavaScript 前端开发 算法
什么是虚拟dom
什么是虚拟dom
|
8天前
|
JavaScript 前端开发 算法
为什么虚拟dom会提高性能?
为什么虚拟dom会提高性能?
25 0