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 代码,因此性能可能会更好,但是这种做法有损灵活性,即用户提供的内容必须编译后才能用。

目录
相关文章
|
1天前
|
缓存 JavaScript 前端开发
Nuxt.js实战:Vue.js的服务器端渲染框架
Nuxt.js提供了开发、构建和部署的完整工作流。使用nuxt命令启动开发服务器,nuxt build进行生产构建,nuxt start启动生产服务器
8 0
|
1天前
|
JavaScript
vue强制刷新组件
vue强制刷新组件
10 0
|
2天前
|
存储 JavaScript 前端开发
vue在页面使用Vue.prototype全局变量
【6月更文挑战第3天】Vue.prototype 是 Vue 实例的原型对象,用于添加全局属性和方法。通过修改 Vue.prototype,所有 Vue 实例都能访问这些属性和方法。例如,可在 mai
5 1
|
2天前
|
JavaScript 前端开发 开发者
vue3+ts配置跨域报错问题解决:> newpro2@0.1.0 serve > vue-cli-service serve ERROR Invalid options in vue.
【6月更文挑战第3天】在 Vue CLI 项目中遇到 &quot;ERROR Invalid options in vue.config.js: ‘server’ is not allowed&quot; 错误是因为尝试在 `vue.config.js` 中使用不被支持的 `server` 选项。正确配置开发服务器(如代理)应使用 `devServer` 对象,例如设置代理到 `http://xxx.com/`: ```javascript module.exports = { devServer: {
15 1
|
2天前
|
缓存 JavaScript 前端开发
|
2天前
|
JavaScript 算法 容器
|
2天前
|
JavaScript 前端开发 API
|
Web App开发 JavaScript 前端开发
Vue框架快速入门
Vue是现在最流行的前端框架之一,而且相对于其他两个框架React和Angular来说也更加易学,而且它的作者是国人,中文文档也很完善。当然Vue框架算是比较高级的框架,所以在使用过程中还需要JavaScript、JavaScript 2015、WebPack、NodeJS、npm、ESLint、JavaScript单元测试框架等其他知识和框架的使用方法。
1302 0