1、Vue 3.0 性能提升主要是通过哪几方面体现的?

  • 响应式系统升级
  • 编译升级
  • 源码体积的优化

在性能方面Vue.js 3.x大幅度提升,使用代理对象Proxy重写了响应式的代码并且对编译器做了优化重写了虚拟DOM,从而让渲染的Update的性能都有了大幅度的提升
另外,官方介绍服务端渲染的性能也提升两到三倍

响应式系统升级

  • Vue.js 2.x中响应式系统的核心defineProperty
    • 初始化时遍历data中的所有成员,通过defineProperty把对象的属性转换成getter和setter,如果data中的属性又是对象的话,需要递归处理每一个子对象的属性。这些都是初始化时进行的,如果你未使用这些属性也会进行响应式的处理
  • Vue.js 3.x中使用Proxy对象重写响应式系统
    • Proxy的性能本身就比defineProperty好,切代理对象可以拦截属性的访问、赋值、删除等操作,不需要初始化时遍历所有的属性,如果有多层属性嵌套只有访问某个属性时才会递归处理下一级属性
    • 使用Proxy对象默认可以监听动态新增的属性,而Vue.js 2.x想要动态添加响应式属性需要调用Vue.set方法来处理
    • Vue.js 2.x监听不到属性的删除
    • Vue.js 2.x对数组的索引和length属性也监听不到

除了响应式系统的升级,Vue.js 3.x通过优化编译的过程和重写虚拟DOM让首次渲染和更新的性能有了大幅度提升

编译升级

image.png
Vue.js 2.x模板首先需要编译成render函数,这个过程一般在构建时完成,在编译时会编译静态根节点和静态节点,静态根节点要求节点中必须有一个静态子节点
当组件的状态发生变化后会通知watcher触发update去执行,虚拟DOM的patch操作,遍历所有的虚拟节点找到差异更新到真实DOM上,diff的过程中会去比较整个虚拟DOM,先对比新旧div以及它的属性再对比内部子节点
Vue.js 2.x中渲染最小的单位是组件,diff的过程会跳过静态根节点,因为惊天大根节点的内容不会发生变化,即

  • Vue.js 2.x中通过标记静态根节点,优化diff的过程,但是静态节点还需要进行diff,没有被优化
  • Vue.js 3.x中标记和提升所有静态根节点,diff的时候只需要对比动态节点内容

    • Fragments(VS Code需要升级vetur插件):模板中不需要再创建唯一的根节点,可以直接放文本内容或者多个同级标签
    • 静态提升
    • Patch flag
    • 缓存事件处理函数

      源码体积的优化

  • Vue.js 3.x中移除了一些不常用的API

    • 例如:inline-template、filter等
  • tree-shaking

2、Vue 3.0 所采用的 Composition Api 与 Vue 2.x使用的Options Api 有什么区别?

  • OptionsAPI
    • 包含一个描述组件选项(data、methods、props等)的对象
    • Options API开发复杂组件,同一个功能逻辑的代码被拆分到不同选项
  • Composition API
    • Vue.js 3.x新增的一组API
    • 一组基于函数的API
    • 可以更灵活的组织组件的逻辑

相对于OptionsAPI这样做的好处:查看某个逻辑时只需关注具体的函数即可,当前的逻辑代码都封装在函数内部,不像OptionsAPI时获取功能代码的位置分散在不同位置,查看这部分代码还需要上下拖动查看

3、Proxy 相对于 Object.defineProperty 有哪些优点?

  1. proxy更为强大

Object.defineProperty()只能见识属性的读写,Proxy能监视到更多的对象操作。例如delete操作、对对象方法的调用等。

  1. const person = {
  2. name: 'peiyp',
  3. age: 27
  4. }
  5. const personProxy = new Proxy(person, {
  6. deleteProperty(target, property) {
  7. // 代理目标对象,要删除的属性名称
  8. console.log('delete', property)
  9. delete target[property]
  10. }
  11. })
  12. delte personProxy.age
  13. console.log(person)
  1. delete age
  2. { name: 'peiyp' }

这也就表明Proxy确实能做到defineProperty做不到的事情,除了delete以外还有许多其他的对象操作都可以监视到。
image.png

  1. Proxy更好的支持数组对象的监视

以往想通过Object.defineProperty()去监视数组的操作,最常见的一种方式就是通过重写数组的操作方法(vue.js使用的方式),大体的思路就是通过自定义的方法去覆盖掉数组原先对象的push、shift等方法一次去劫持对应这个方法调用的过程。如何使用Proxy对象监视数组:

  1. const list = []
  2. const listProxy = new Proxy(list, {
  3. // 监视数据写入
  4. set(target, property, value) {
  5. // 得到的属性名 属性值
  6. console.log('set', property, value)
  7. // 设置目标对象当中所对应的属性
  8. target[property] = value
  9. // 表示设置成功
  10. return true
  11. }
  12. })
  13. listProxy.push(100)
  14. listProxy.push(100)
  1. set 0 100
  2. set length 1
  3. set 1 100
  4. set length 2

这里的0实际上就是数组当中的下标,100就是0这个下标对应的值。这也就表示Proxy内部会自动根据push操作去推算出来他应该所处的下标。数组的其他操作方式都是类似的。

  1. Proxy是以非入侵的方式监管了对象的读写

也就是说一个已经定义好的对象,不需要对对象本身做任何操作就可以监视到内部成员的读写。而Object.defineProperty()就要求我们必须通过特定的方式单独定义对象中需要被监视的属性。

  1. // defineProperty方式
  2. const person = {}
  3. Object.defineProperty(person, 'name', {
  4. get() {
  5. console.log('name被访问')
  6. return person_name
  7. },
  8. set(value) {
  9. console.log('name被设置')
  10. peroson._name = value
  11. }
  12. })
  13. Object.defineProperty(person, 'age', {
  14. get() {
  15. console.log('age被访问')
  16. return person_age
  17. },
  18. set(value) {
  19. console.log('age被设置')
  20. peroson._age = value
  21. }
  22. })
  23. person.name = 'jack'
  24. console.log(person.name);
  25. -------------------------------
  26. name被设置
  27. name被访问
  28. jack
  1. // Proxy方式更为合理
  2. const person2 = {
  3. name: 'jack',
  4. }
  5. const personProxy = new Proxy(person2, {
  6. get (target, property) {
  7. console.log('get', property)
  8. return target[property]
  9. },
  10. set (target, property, value) {
  11. console.log('set', property, value)
  12. target[property] = value
  13. }
  14. })
  15. personProxy.name = 'jack'
  16. console.log(personProxy.name)
  17. -------------------------------------
  18. set name jack
  19. get name
  20. jack

4、Vue 3.0 在编译方面有哪些优化?

Vue.js 2.x模板首先需要编译成render函数,这个过程一般在构建时完成的,在编译时会编译静态根节点和静态节点,静态根节点要求节点中必须有一个静态子节点。当组件的状态发生变化后会通知watcher触发update去执行虚拟DOM的patch操作,遍历所有的虚拟节点找到差异更新到真是DOM上,diff过程中会去比较整个虚拟DOM,先对比新旧div以及他的属性再对比内部子节点。Vue.js 2.x中渲染最小的单位是组件,diff的过程会调过静态根节点,因为静态根节点的内容不会发生变化,即

  • Vue.js 2.x中通过标记静态根节点,优化diff的过程,但是静态节点还需要进行diff,没有被优化
  • Vue.js 3.x中标记和提升所有静态根节点,diff的时候只需要对比动态节点内容
    • Fragments(VS Code需要升级vetur插件):模板中不需要再创建唯一的根节点,可以直接放文本内容或者多个同级标签
    • 静态提升
    • Patch flag
    • 缓存事件处理函数

5、Vue.js 3.0 响应式系统的实现原理?

  • Proxy对象实现属性监听
    • Vue3重写了响应式系统,和Vue2相比Vue3的响应式系统底层采用Proxy对象实现。在初始化的时候不需要遍历所有的属性,再把属性通过defineProperty转换成getter和setter
  • 多层嵌套,在访问属性过程中处理下一级属性
    • 如果有多层属性嵌套的话,只有访问某个属性的时候才会递归处理下一级属性,所有Vue3中响应式系统的性能要比Vue2好
  • 默认监听动态添加的属性
  • 默认监听属性的删除操作
  • 默认监听数组索引和length属性
  • 可以作为单独的模块使用

核心方法:

  • reactive、ref、toRefs、computed
  • effect
  • track
  • trigger

    watch/watchEffect是Vue3的runtime.core中实现的,watch函数的内部其实实现了一个底层函数effect,我们会模拟实现effect函数以及Vue3中收集依赖和触发更新的函数track和trigger