vue2升级到vue3带来的改变
尤雨溪:重头来过的 Vue 3 带来了什么?
重写 Vue 新的主要版本的两个关键的因素:
- 主流浏览器对新的 JavaScript 语言特性的普遍支持。
- 当前 Vue 代码库随着时间的推移而暴露出来的设计和体系架构问题。
重写带来的技术性变化:
双向数据绑定
vue2
通过 Object.defineProperty
将对象属性转化为 getter/setter
, 该属性是 ES5 中无法被 shim 的特性,也是 vue 不支持 IE8 及以下版本浏览器的原因。
该方法存在的问题:
在 vue 中, Object.defineProperty
无法监控到数据的下标变化,导致直接通过数组下标给数组设置新值时,无法做到实时响应。目前 vue 只针对数组的变异方法 push/pop/shift/unshift/splice/sort/reverse
做了 hack 处理,存在响应局限。
vue3
Proxy
是 ES6
中新增的一个特性,翻译过来意思是"代理",用在这里表示由它来“代理”某些操作。 Proxy
让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。
Proxy
可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。
通过 es2015 中的新特性 Proxy
使用,在框架中拦截针对对象(属性)的操作.
const arr = [1, 2, 3]
const handler = {
get(target, property) {
console.log(`${property}被读取了`)
return target[property]
},
set(target, property, value) {
console.log(`${property} 被设置为 ${value}`)
target[property] = value
}
}
let a = new Proxy(arr, handler)
a[2] // 2被读取了
a[3] = 4 // 3 被设置为 4
arr // [1, 2, 3, 4]
对 ts 的支持
vue2 最初是使用纯 ES(Javascript)
写成的,而没有引入类型检查系统。类型检查能有效减少重构过程中引入错误的机会,虽然后续采用了 Facebook
的 Flow type checker
, 但没有明显的改观,相比较 TypeScript
与 Visual Studio Code
集成开发工具的深度集成,Flow type checker
对集成开发环境的支持也不理想。切换到 TypeScript
将允许我们自动生成声明文件,从而减轻维护负担。
虚拟 dom 渲染
Vue 有一个相当独特的渲染策略:它提供类似于 HTML 的模板语法,但是,它是将模板编译成渲染函数来返回虚拟 DOM 树。Vue 框架通过递归遍历两个虚拟 DOM 树,并比较每个节点上的每个属性,来确定实际 DOM 的哪些部分需要更新。由于现代 JavaScript 引擎执行的高级优化,这种有点暴力的算法通常非常快速,但是 DOM 的更新仍然涉及许多不必要的 CPU 工作。当你看到一个基本上是静态内容、只有少量动态绑定的模板时,效率低下的情况尤其明显,因为这时候仍然需要递归地遍历整个虚拟 DOM 树,以找出需要更改的内容。
幸运的是,模板编译步骤使我们有机会对模板执行静态分析并提取有关动态部分的信息。Vue 2 在某种程度上是通过跳过静态子树来实现的,但是过于简单的编译器体系架构使得更高级的优化很难实现。在 Vue 3 中,我们使用适当的 AST 转换管道重写编译器,这允许我们以转换插件的形式将编译时(compile-time)优化组合进来。
随着新的体系架构的出现,我们希望找到一种能够尽可能减少开销的渲染策略。一种选择是抛弃虚拟 DOM 并直接生成命令式 DOM 操作,但这样做会消除直接编写虚拟 DOM 渲染函数的能力,而我们发现这种能力对于高级用户和库的编写者非常有价值。另外,这将是一个巨大的突破性改变。
另一个更好的办法是去掉不必要的虚拟 DOM 树遍历和属性比较,这在更新期间往往会产生最大的性能开销。为了实现这一点,编译器和运行时需要协同工作:编译器分析模板并生成带有优化提示的代码,而运行时尽可能获取提示并采用快速路径。这里有三个主要的优化:
首先,在 DOM 树级别。我们注意到,在没有动态改变节点结构的模板指令(例如 v-if 和 v-for)的情况下,节点结构保持完全静态。如果我们将一个模板分成由这些结构指令分隔的嵌套“块”,则每个块中的节点结构将再次完全静态。当我们更新块中的节点时,我们不再需要递归遍历 DOM 树 - 该块内的动态绑定可以在一个平面数组中跟踪。这种优化通过将需要执行的树遍历量减少一个数量级来规避虚拟 DOM 的大部分开销。
其次,编译器积极地检测模板中的静态节点、子树甚至数据对象,并在生成的代码中将它们提升到渲染函数之外。这样可以避免在每次渲染时重新创建这些对象,从而大大提高内存使用率并减少垃圾回收的频率。
第三,在元素级别。编译器还根据需要执行的更新类型,为每个具有动态绑定的元素生成一个优化标志。例如,具有动态类绑定和许多静态属性的元素将收到一个标志,提示只需要进行类检查。运行时将获取这些提示并采用专用的快速路径。
综合起来,这些技术大大改进了我们的渲染更新基准,Vue 3 有时占用的 CPU 时间不到 Vue 2 的十分之一。
注:CPU 时间指的是执行 JavaScript 计算所花费的时间,不包括浏览器 DOM 操作。