从底层了解Vue3

Vue3带来了什么(新特性)

众所周知,前端的技术一直更新的特别快,而我们作为前端开发者也应该快速适应,主动拥抱变化,推陈出新,与时俱进

在这里插入图片描述

  • 重写了虚拟Dom实现

  • 编译模板的优化

  • 更高效的组件初始化

  • undate性能提高1.3~2倍

  • SSR速度提高了2~3倍

在这里插入图片描述

  • 增强可维护性
    TypeScript + modularized internals 基于TS语言编写和模块化更强的核心代码

  • 性能提升
    Proxy-based Reactivity System 基于代理的响应式系统
    Compiler-informed Virtual DOM & SSR 编译推断的虚拟Dom和服务器渲染

  • 更小体积
    Tree-shaking 按需裁剪
    Compile-time flags 编译时标志

  • Scales better 更好的可伸缩性
    组合式API

  • Better DX New Single-file Component improvements 新的单文件组件性能改进
    Modularized Internals 模块化内核

更快

1. 重写虚拟dom,diff算法优化

回顾Vue2的vdom:

  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  3. 把所记录的差异应用到所构建的真正的DOM树上,视图就更新了

vue2.x的diff算法叫做全量比较,顾名思义,就是当数据改变的时候,会从头到尾的进行vDom对比,即使有些内容是永恒固定不变的,它也会一一遍历。当模板中节点数量不断增加时(例如静态节点有上千个),此时所消耗的性能也会增加,特别是对静态节点的遍历,做了‘无用功’。

所以vue3 针对此问题,diff算法使用了一个全新的静态标记(PatchFlag),在此基础上做出了优化:

  1. 标记模板中的静态内容,区别了模板中的静态和动态节点
  2. 更新时,只diff操作动态的内容
  3. Vue3中会首先区分出以上模板的静态节点和动态节点,当视图更新时,只对动态节点部分进行diff运算,减少了资源的损耗

以下是Vue3遍历节点后编译的虚拟dom
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们发现创建动态 dom 元素的时候,Vdom 除了模拟出来了它的基本信息之外,还给它加了一个标记: 1 /* TEXT */

export const enum PatchFlags {
  TEXT = 1,// 动态文本节点
  CLASS = 1 << 1, // 2  // 动态 class
  STYLE = 1 << 2, // 4 // 动态 style
  PROPS = 1 << 3, // 8 // 动态属性,但不包含类名和样式
  FULL_PROPS = 1 << 4, // 16 // 具有动态 key 属性,当 key 改变时,需要进行完整的 diff 比较。
  HYDRATE_EVENTS = 1 << 5, // 32 // 带有监听事件的节点
  STABLE_FRAGMENT = 1 << 6, // 64 // 一个不会改变子节点顺序的 fragment
  KEYED_FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或部分子字节有 key
  UNKEYED_FRAGMENT = 1 << 8, // 256 // 子节点没有 key 的 fragment
  NEED_PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比较
  DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot
  HOISTED = -1, // 静态节点
  // 指示在 diff 过程应该要退出优化模式
  BAIL = -2
}

这个标记就叫做 patch flag(补丁标记),patch flag 的强大之处在于,当你的 diff 算法走到 _createBlock 函数的时候,会忽略所有的静态节点,只对有标记的动态节点进行对比,而且在多层的嵌套下依然有效。

这个也叫做Vue3的静态提升,它包括两部分(静态树的提升,静态属性的提升)

同样的,静态属性的含义也是如此:
在这里插入图片描述

在这里插入图片描述
我们会发现原本的标记变成了 9 /* TEXT, PROPS */,而且后边多了一个数组 [“id”]

这里的 patch flag 中的注释的内容告诉我们,这一个 dom 元素不止有内容 TEXT 会变化,它的属性 PROPS 也会变化。而后边的数组中的内容则是有可能发生变化的属性。

事件侦听器缓存
对于事件绑定来说,在默认情况下onClick会被视为动态绑定会使得vue每次都会跟踪他的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可

关闭 cacheHandlers

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", _hoisted_1, [
    _createVNode("button", { onClick: _ctx.confirmHandler }, "确认", 8 /* PROPS */, ["onClick"]),
    _createVNode("span", null, _toDisplayString(_ctx.vue3), 1 /* TEXT */)
  ]))
}
// 取消了缓存

开启 cacheHandlers

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", _hoisted_1, [
    _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = $event => (_ctx.confirmHandler($event)))
    }, "确认"),
    _createVNode("span", null, _toDisplayString(_ctx.vue3), 1 /* TEXT */)
  ]))
}
// 首次渲染时, 将事件缓存在_cache[1]中
// 再次调用时, 判断是否有缓存,如果有直接从缓存中获取事件,无需再次创建更新,减少消耗 

2. 使用Proxy代替Object.defineProperty

vue2利用Object.defineProperty来劫持data数据的getter和setter操作。这使得data在被访问或赋值时,动态更新绑定的template模块,来达到数据双向绑定的目的

vue2中Object.defineProperty的缺陷:

  1. 无法原生监听数组的变化,需要特殊处理
  2. 必须遍历对象的每个属性(当示例初始化的时,Object.definePropety是从data的根节点遍历到末节点。一次性便利全部)
  3. 无法监听属性的新增删除操作(VUE提供Vue.set Vue.delete API,原因就是因为Object.definePropety无法监听新增删除操作)
let p = new Proxy(target, handler);

Proxy对象是ES2015(ES6)引入的一个原生对象,proxy在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截。因此它能监听数组,监听新增删除操作,深层遍历嵌套的对象等。
Proxy构造函数的第一个参数是原始数据target,是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
第二个参数是一个叫handler的处理器对象。其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。Handler是一系列的代理方法集合,它的作用是拦截所有发生在data数据上的操作。
p是代理后的对象,当外界每次对 p 进行操作时,就会执行 handler 对象上的一些方法。Proxy有多达13种劫持操作。包括常用的get()和set()是最常用的两个方法,分别代理访问和赋值两个操作。

但因为浏览器兼容所限,使用proxy会放弃对于IE的支持,vue只能使用Object.defineProperty实现双向绑定。这在当时是一种很前卫的设计,不过随着浏览器的不断迭代,这种技术在api和性能上愈发跟不上时代的步调。
到目前为止,各大主流移动端浏览器,PC端浏览器,包括新一代window产品内置Microsoft Edge都满足对于Vue3的支持。其次,若真的需要满足支持IE及极少数低版本浏览器,还可以使用垫片工具(es6-proxy-polyfill)。

更小

Tree shaking

什么是Tree shaking?

Treeshaking是一个术语,通常用于描述移除JavaScript上下文中的未引用代码(dead-code),就像一棵大树,将那些无用的叶子都摇掉。
它依赖于ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具rollup。

Tree shaking的原理:

使用Tree shaking的原理是ES6的模块静态分析引入,这就可以在编译时正确判断到底加载了什么代码,但是要注意import 和export是ES6原生的而不是通过babel或者webpack转化的

Tree shaking 与 按需导入的区别?

按需引入是在babel编译过程中,按需只引入相关代码其本质是在babel编译阶段将部分代码做了替换
Tree shaking是在webpack打包阶段,移除 JavaScript 上下文中的未引用代码

Tree shaking在Vue3.0中的使用:
我们在vue3的写法上可以发现,当我们要使用到一些API方法时,我们需要不断重复的引入这些方法

import { reactive, onMounted, ref, toRefs } from 'vue'

在Vue3中,对代码结构进行了优化,让其更加符合Tree shaking的结构,Vue3.x中的核心API都支持tree-shaking,这些API都是通过包引入的方式而不是直接在实例化时就注入,只会对使用到的功能或特性进行打包(按需打包),这意味着更多的功能和更小的体积。

更易维护

组合式API(Composition API)

从Options API(选项式API)到Composition API (组合式API)(函数式API)
在这里插入图片描述
composition api 把复杂组件的逻辑抽地更紧凑,而且可以将公共逻辑进行抽取,提高代码逻辑的可复用性,从而实现与模板的无关性;同时使代码的可压缩性更强。另外,把 Reactivity 模块独立开来,意味着 Vue3.0 的响应式模块可以与其他框架相组合。

setup
①setup()函数是vue3中专门新增的方法,可以理解为Composition Api的入口;
②setup在组件创建之前调用的,也就是在beforeCreate、created之前创建,因此没有this;
③setup 函数在创建组件之前被调用,所以在 setup 被执行时,组件实例并没有被创建;因此在 setup 函数中,我们将 没有办法 获取到 this 。

options api 与 composition api 取舍:
两者能实现共存,能混合写在setup中

Composition API 除了在逻辑复用方面有优势,也会有更好的类型支持,因为它们都是一些函数,在调用函数时,自然所有的类型就被推导出来了,不像 Options API 所有的东西使用 this。另外,Composition API 对 tree-shaking 友好,代码也更容易压缩。vue3的Composition API会将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去

更好的TypeScript支持

Vue3.x采用TypeScript重写,开发者使用Vue3.x时可以充分体验TS给编码带来的便利。

经过对于原有项目的vue3升级后,我进行了打包和运行的测试,如下:

在这里插入图片描述
在这里插入图片描述
因为都是使用到webpack打包工具,所以打包出的大小差别无异,后续我们能测试下Vue3的黄金搭档vite2的能力

Vue2 运行速度:

在这里插入图片描述
Vue3运行速度:
在这里插入图片描述
从整个页面完成时间来看,由1.58S到1.39S
从DOM文档结构加载完毕时间来看,由327ms到148ms

vue3推出时给出的数据是更新相比于vue2有1.3~2倍的性能优势
特别是在dom更新的速度上,突出更为明显

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值