Vue设计与实现 框架设计概览 权衡的艺术

简介: Vue设计与实现 框架设计概览 权衡的艺术

1.1 命令式和声明式

从范式上来看,视图层框架通常分为命令式和声明式,它们各有优缺点。

jQuery 就是典型的命令式框架。命令式框架的一大特点就是关注过程。

声明式框架更加关注结果。

Vue.js 的内部实现一定是命令式的,而暴露给用户的却更加声明式。

1.2 性能与可维护性的权衡

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

理论上命令式代码可以做到极致的性能优化,因为我们明确知道哪些发生了变更,只做必要的修改就行了。但是声明式代码不一定能做到这一点,因为它描述的是结果。

对于框架来说,为了实现最优的更新性能,它需要找到前后的差异并只更新变化的地方。

但是最终完成这次更新的代码仍然是依然是命令式代码。

如果我们把直接修改的性能消耗定义为 A,把找出差异的性能消耗定义为 B,那么有:

命令式代码的更新性能消耗 = A

声明式代码的更新性能消耗 = B + A

最理想的情况是,当找出差异的性能消耗为 0 时,声明式代码与命令式代码的性能相同,但是无法做到超越,框架本身就是封装了命令式代码才实现了面向用户的声明式。这符合前文中给出的性能结论:声明式代码的性能不优于命令式代码的性能。

声明式代码的可维护性更强。声明式代码展示的就是我们要的结果,看上去更加直观。

在保持可维护性的同时让性能损失最小化。

在采用声明式提升可维护性的同时,性能就会有一定的损失,而框架设计者要做的就是:在保持可维护性的同时让性能损失最小化。

1.3 虚拟 DOM 的性能到底如何

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

虚拟 DOM,就是为了最小化找出差异这一步的性能消耗而出现的。

虚拟 DOM要解决的问题:让我们不用付出太多的努力(写声明式代码),还能够保证应用程序的性能下限,让应用程序的性能不至于太差,甚至想办法逼近命令式代码的性能呢?

使用 innerHTML 操作页面和虚拟 DOM 相比性能如何?

DOM 的运算要远比 JavaScript 层面的计算性能差

image.png

innerHTML 创建页面的性能:HTML 字符串拼接的计算量 + innerHTML 的 DOM计算量。

虚拟 DOM 创建页面的过程分为两步:第一步是创建 JavaScript 对象,这个对象可以理解为真实 DOM 的描述;第二步是递归地遍历虚拟 DOM 树并创建真实 DOM。我们同样可以用一个公式来表达:创建 JavaScript 对象的计算量 + 创建真实 DOM 的计算量。

image.png

无论是纯 JavaScript 层面的计算,还是 DOM 层面的计算,其实两者差距不大。这里我们从宏观的角度只看数量级上的差异。如果在同一个数量级,则认为没有差异。在创建页面的时候,都需要新建所有 DOM 元素。

创建页面时的性能情况,大家可能会觉得虚拟 DOM 相比 innerHTML 没有优势可言,甚至细究的话性能可能会更差。

使用 innerHTML 更新页面的过程是重新构建 HTML 字符串,再重新设置 DOM 元素的 innerHTML 属性。重新设置 innerHTML 属性就等价于销毁所有旧的 DOM 元素,再全量创建新的 DOM 元素。

虚拟 DOM 是如何更新页面的。它需要重新创建 JavaScript 对象(虚拟 DOM 树),然后比较新旧虚拟 DOM,找到变化的元素并更新它。

image.png

在更新页面时,虚拟 DOM 在 JavaScript 层面的运算要比创建页面时多出一个 Diff 的性能消耗,然而它毕竟也是 JavaScript 层面的运算,所以不会产生数量级的差异。再观察 DOM 层面的运算,可以发现虚拟 DOM 在更新页面时只会更新必要的元素,但 innerHTML 需要全量更新。这时虚拟 DOM 的优势就体现出来了。

当更新页面时,影响虚拟 DOM 的性能因素与影响 innerHTML 的性能因素不同。对于虚拟 DOM 来说,无论页面多大,都只会更新变化的内容,而对于 innerHTML 来说,页面越大,就意味着更新时的性能消耗越大。

image.png

innerHTML 和 document.createElement 等 DOM 操作方法有何差异?

innerHTML、虚拟 DOM 以及 原生 JavaScript(指 createElement 等方法)在更新页面时的性能。

image.png

我们分了几个维度:心智负担、可维护性和性能。

原生 DOM 操作方法的心智负担最大,因为你要手动创建、删除、修改大量的 DOM 元素。但它的性能是最高的,不过为了使其性能最佳,我们同样要承受巨大的心智负担。以这种方式编写的代码,可维护性也极差。

innerHTML 来说,由于我们编写页面的过程有一部分是通过拼接 HTML 字符串来实现的,这有点儿接近声明式的意思,但是拼接字符串总归也是有一定心智负担的,而且对于事件绑定之类的事情,我们还是要使用原生 JavaScript 来处理。如果 innerHTML 模板很大,则其更新页面的性能最差,尤其是在只有少量更新时。

虚拟 DOM,它是声明式的,因此心智负担小,可维护性强,性能虽然比不上极致优化的原生 JavaScript,但是在保证心智负担和可维护性的前提下相当不错。

1.4 运行时和编译时

纯运行时的框架

不能支持用类似于 HTML 标签的方式描述树型结构的数据对象。

没有编译的过程,因此我们没办法分析用户提供的内容

<script>
  function Render(obj, root) {
    const el = document.createElement(obj.tag)
    if (typeof obj.children === 'string') {
      const text = document.createTextNode(obj.children)
      el.appendChild(text)
    } else if (obj.children) {
      // array,递归调用 Render,使用 el 作为 root 参数
      obj.children.forEach((child) => Render(child, el))
    }
    // 将元素添加到 root
    root.appendChild(el)
  }
  const obj = {
    tag: 'div',
    children: [
      { tag: 'span', children: 'hello world' }
    ]
  }
  // 渲染到 body 下
  Render(obj, document.body)
</script>
运行时 + 编译时的框架

它既支持运行时,用户可以直接提供数据对象从而无须编译;

如果加入编译步骤,可以分析用户提供的内容,看看哪些内容未来可能会改变,哪些内容永远不会改变,这样就可以在编译的时候提取这些信息,然后将其传递给 Render 函数,Render 函数得到这些信息之后,就可以做进一步的优化了。

又支持编译时,用户可以提供 HTML 字符串,我们将其编译为数据对象后再交给运行时处理。准确地说,上面的代码其实是运行时编译,意思是代码运行的时候才开始编译,而这会产生一定的性能开销,因此我们也可以在构建的时候就执行 Compiler 程序将用户提供的内容编译好,等到运行时就无须编译了,这对性能是非常友好的。

image.png

Compiler 函数(把 HTML 字符串编译成树型结构的数据对象)和 Render 函数(操作DOM进行渲染)

Vue.js 3 仍然保持了运行时 + 编译时的架构

纯编译时的框架

image.png

Compiler 函数

纯编译时的,那么它也可以分析用户提供的内容。由于不需要任何运行时,而是直接编译成可执行的 JavaScript 代码,因此性能可能会更好,但是这种做法有损灵活性,即用户提供的内容必须编译后才能用。

目录
相关文章
|
22天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
24天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
26天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
31 1
vue学习第一章
|
22天前
|
缓存 监控 JavaScript
Vue.js 框架下的性能优化策略与实践
Vue.js 框架下的性能优化策略与实践
|
22天前
|
JavaScript 前端开发 开发者
Vue是如何劫持响应式对象的
Vue是如何劫持响应式对象的
21 1
|
22天前
|
JavaScript 前端开发 API
介绍一下Vue中的响应式原理
介绍一下Vue中的响应式原理
27 1
|
22天前
|
JavaScript 前端开发 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
22天前
|
存储 JavaScript 前端开发
介绍一下Vue的核心功能
介绍一下Vue的核心功能
|
26天前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
24 1
vue学习第三章
|
26天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
35 1
vue学习第四章