面试题(Vue)

58 阅读22分钟
1. vue3中构造函数和普通函数的区别?
  • vue2 中使用构造函数特点:
    • 通过 new关键字调用
    • 通常以大写字母开头(命名约定)
    • 返回新创建的对象实例
  • Vue 3 已经从 Vue 2 的构造函数模式转变为工厂函数/普通函数模式
    • 这种转变带来了更好的开发体验、更好的 TypeScript 支持、更好的包体积优化,以及更灵活的 API 设计。
    • 现在几乎所有的 Vue 3 API 都是通过普通函数提供的,这使得代码更加函数式,也更容易进行组合和测试
      • 直接调用,无需 new
      • 通常返回对象或值
2. cookie与session的区别?
  • 核心的区别在于:数据的存储位置不同
    • Cookie:数据存储在客户端
    • Session:数据存储在服务器端
  • 工作流程
    • 浏览器第一次访问服务器。
    • 服务器为该用户创建一个唯一的 Session ID,并在内存或数据库中创建一个存储空间(Session 对象)与之关联。
    • 服务器在响应头中通过 Set-Cookie,将这个 Session ID​ 返回给浏览器(例如:JSESSIONID=abc123)。
    • 浏览器将此 Session ID 作为 Cookie 保存。
    • 此后浏览器的每次请求,都会带上这个包含 Session ID 的 Cookie。
    • 服务器收到请求后,通过传过来的 Session ID,找到对应的那个“档案袋”(Session 对象),从而读取或修改里面的用户数据。
3. slot的使用?
  • 使用场景
    • 通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理
    • 如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情通过 slot 插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用比如布局组件、表格列、下拉选、弹框显示内容等
  • 分类
    • 默认插槽
      • 子组件有一个空位使用slot标签来占位,可以随便放东西进来填充,如果父组件不放,就展示子组件自己准备的 slot 标签内的内容
    • 具名插槽
      • 子组件用 name 属性来表示插槽的名字,不传为默认插槽
      • 父组件中在使用时在默认插槽的基础上加上slot 属性,值为子组件插槽 name 属性值
    • 作用域插槽 *
4. computed是怎么做响应式的?
  • 首次访问计算属性

    • 触发 getter函数
    • 执行 evaluate()进行实际计算
    • 计算过程中访问的响应式数据会将此计算 Watcher 收集为依赖
    • 计算结果被缓存,dirty标记设为 false
  • 后续访问(依赖未变)

    • 直接返回缓存值,不重新计算
  • 依赖数据变更

    • 依赖数据的 setter 通知所有依赖它的 Watcher
    • 计算 Watcher 收到通知后标记自身 dirty = true
    • 不立即重新计算(惰性原则)
  • 视图更新触发重新计算

    • 渲染 Watcher 执行更新
    • 访问计算属性时发现 dirty=true
    • 触发重新计算并更新缓存
5. vue2和vue3的区别?

image.png

6. MVVM
  1. MVC Model (模型) View (视图) Controller (控制器)
  • 通信流程:
    • 用户操作 View(如点击按钮)。
    • View 将用户输入事件传递给 Controller。
    • Controller 处理用户输入(可能涉及验证、转换等)。
    • Controller 调用 Model 的方法来更新数据或执行业务逻辑。
    • Model 更新自身状态(可能通知注册的观察者,通常是 View)。
    • 更新 View 的两种主要方式:
      • Observer Pattern (观察者模式): ​ View 注册为 Model 的观察者。Model 改变后直接通知 View,View 从 Model 拉取新数据并更新自身。(更常见于经典 MVC)
      • Controller 更新: ​ Controller 在 Model 更新后,主动选择并更新相应的 View。
  1. MVP
    • Model (模型) View (视图) Presenter (呈现器)
      • MVC 的演变,旨在解决 MVC 中 Controller 职责过重和 View-Model 耦合问题。
      • 它完全切断了 View 和 Model 的直接联系
      • Presenter (呈现器)是MVP 的核心。它充当 View 和 Model 之间的中间人
  2. MVVM
  • Model (模型) View (视图) ViewModel (视图模型)

    • ViewModel

      • 它包含 View 所需的数据(通常是 Model 数据的转换形式)和状态(如加载中、错误信息)。

      • 它暴露属性(Properties)供 View 绑定(通常是可观察的 Observable Properties)。

      • 它暴露命令(Commands)供 View 绑定用户操作(如 ICommand)。

      • 调用 Model 的方法来执行业务逻辑。

      • 监听 Model 的变化(或接收 Model 的更新通知),并相应地更新自己的属性。由于数据绑定,View 会自动更新。

      • 它不持有对 View 的直接引用! ​ 通信通过绑定机制完成。

    • 优点:

      • View 和 ViewModel 解耦:通过绑定机制连接,无需直接引用。
      • 极低的 View 代码量:UI 更新由框架自动处理,开发者只需声明绑定关系。
      • 高可测试性:ViewModel 独立于 View 和 UI 框架,易于单元测试。
      • 开发效率高:数据绑定减少了大量样板代码(手动更新 UI)。
    • 缺点:

      • 数据绑定可能带来调试复杂性(需要理解绑定机制和可能的错误)。
      • 过度或不合理的使用数据绑定可能导致性能问题(如过多不必要的通知)。
      • 对于非常复杂的 UI 逻辑,ViewModel 的设计可能变得复杂。
7. 数据双向绑定原理?

vue2 的响应式实现主要是利用了Object.defineProperty方法里面的 setter 与 getter 方法的观察者模式来实现。在组件初始化时会给每一个 data 属性注册getter和setter,然后再new 一个自己的watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DoM。在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的watcher对象注册进 sub 里。当data属性发生改变之后,就会遍历 sub 里所有的 watcher 对象,通知它们去重新渲染组件。

vue3 的响应式系统基于 Proxy 实现。在创建响应式对象时,会为其创建一个 Proxy 代理,拦截 get、set 等操作。组件初始化时会创建一个 ReactiveEffect(渲染 effect)来执行组件的渲染函数。当渲染函数执行访问响应式数据时,会触发 Proxy 的 get 拦截器,将当前 effect 收集为依赖。当响应式数据变化时,会触发 Proxy 的 set 拦截器,通知所有相关 effect 重新执行,从而触发组件重新渲染

8. computed,watch,methods
  1. 作用机制上
    1. watch和computed都是以Vue的依赖追踪机制为基础的,它们都试图处理这样一件事情:当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的相关数据自动发生变化,也就是自动调用相关的函数去实现数据的变动。
    2. 对methods:methods里面是用来定义函数的,很显然,它需要手动调用才能执行。而不像watch和computed自动执行预先定义的函数。
  2. 从性质上来看
    1. methods里面定义的是函数,你显然需要像fuc()这样去调用它
    2. computed是[计算属性]事实上和data里声明的数据在使用上是相同的
    3. watch:类似于监听机制+事件机制
  3. methods很明显区别于 computed 与 watch,那么 computed 与 watch 有什么区别呢?
    1. watch擅长处理的场景:一个数据影响多个数据
    2. computed擅长处理的场景:一个数据受多个数据影响
9. v-if和v-show的区别?
  1. v-if 和 v-show 是 Vue.js 中用于条件渲染的指令,它们的作用是根据条件来控制元素的显示和隐藏。 它们之间有一些重要的区别:

  2. 编译时刻 vs 运行时刻:

    • v-if 是一个"惰性"指令,在编译时刻,Vue.js 会根据条件决定是否编译或挂载元素到 DOM 中。如果条件为 false ,元素根本不会被编译和渲染到 DOM 中。
    • v-show 是一个"非惰性"指令,在编译时刻,元素总是会被编译和渲染到 DOM 中。但是,根据条件的值, v-show 会通过 CSS 控制元素的显示和隐藏,不会从 DOM 中移除元素。

3.显示隐藏方式:

  • v-if 在条件为 true 时会渲染元素到 DOM, 而在条件为 false 时会从 DOM 中移除元素。 v-if 也可以触发组件创建和销毁的生命钩子。
  • v-show 在条件为 true 时会通过 CSS 设置元素的 display 属性为可见(通常是display:block ),在条件为 false 时设置为隐藏( display:none )。元素始终存在于 DOM 中,只是通过 CSS 控制其显示状态。

4.切换开销:

  • v-if 在条件切换时,如果条件从true 切换为。false,会销毁并重新创建元素,这涉及到 DOM 的删除和重新插入,可能会有一定的性能开销。
  • v-show 在条件切换时,只是简单地通过 CSS 控制元素的显示和隐藏,不会销毁和重新创建元素,因此切换的开销较小。

5.初始渲染开销:

  • v-if 在初始渲染时,如果条件为false,元素不会被渲染到 DOM 中,因此在初始渲染时可能会有一定的性能优势。
  • v-show 在初始渲染时,元素总是会被渲染到 DOM 中,因此在初始渲染时可能会有一些额外的开销。

综上所述,当需要频繁切换元素的显示状态时,且元素可能处于不同的状态,推荐使用 v-show。而当条件不会频繁改变,且希望在条件为false 时不渲染元素到 DOM 中,推荐使用v-if。在实际使用中,根据具体的场景和性能需求来选择合适的指令。

10. vue特点

Vue.js 是一个渐进式 JavaScript 框架,它的核心特点主要体现在以下几个方面:

  • 响应式数据绑定(双向绑定)
    • 通过 数据劫持(Object.defineProperty) ​ 和 依赖追踪​ 实现。
    • 数据变化时,视图自动更新;反之,视图输入变化也会同步到数据(如 v-model
    • 在 Vue 3 中改为使用 Proxy​ 实现,性能更好且支持更多数据结构
  • 组件化开发
    • 将 UI 拆分为独立、可复用的组件,每个组件包含自己的模板、逻辑和样式。
    • 支持单文件组件(.vue 文件) ,将 HTML、CSS、JavaScript 封装在一个文件中。
    • 组件间通过 props​ 传递数据,通过 emit 事件通信,逻辑清晰。
  • 虚拟 DOM 与高效渲染
    • 通过 虚拟 DOM​ 减少直接操作真实 DOM 的开销。
    • 采用 Diff 算法​ 对比变化,最小化 DOM 操作,提升性能。
  • 声明式模板语法
    • 基于 HTML 的模板语法,通过指令(如 v-ifv-forv-bind)声明式地绑定数据和 DOM
  • 丰富的生态系统
    • 官方支持的路由库(Vue Router)和状态管理库(Vuex/Pinia
    • 构建工具链(Vue CLIVite)和浏览器调试工具(Vue Devtools
    • 适用于多端开发的框架
  • 灵活性 & 渐进式
    • 既可作为轻量库嵌入现有项目,也可用于构建大型单页应用(SPA)
    • 核心库只关注视图层,可逐步集成路由、状态管理等。
11. 单页面应用(spa)?

缺点:

  1. 首次加载耗时比较多。
  2. SEO问题,不利于百度,360等搜索引擎收录。
  3. 容易造成CSS命名冲突。
  4. 前进、后退、地址栏、书签等,都需要程序进行管理,页面的复杂度很高,需要一定的技能水平和开发成本高。
12. data为何是函数?
  1. vue 组件可能会有很多个实例,采用函数返回一个全新 data 形式,使每个实例对象的数据不会受到其他实例对象数据的污染
  2. 根实例对象 data 可以是对象也可以是函数(根实例是单例),不会产生数据污染情况
  3. 组件实例对象 data 必须为函数,目的是为了防止多个组件实例对象之间共用一个 data,产生数据污染。采用函数的形式, initData 时会将其作为工厂函数都会返回全新 data 对象
13. v-for中必须有key为什么?
  • key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速
  • diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.
  • diff程可以概括为:oldChild 和 newChild 各有两个头尾的变量Startldx和Endldx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦Startldx>Endldx表明 oldChild 和 newChild 至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾
  • 准确性:如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug.快速:key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n),Map的时间复杂度仅仅为O(1)
14. vue中的指令?
  1. 数据绑定
    • v-model:双向数据绑定
    • v-bind或:动态绑定属性/Props
  2. 条件渲染
    • v-if/ v-else-if/ v-else:条件渲染
    • v-show:通过 CSS display控制显示
  3. 列表渲染
    • v-for:遍历数组/对象
      • 必须指定 :key
      • 可遍历数组、对象、数字范围
  4. 事件处理
    • v-on或 @:绑定事件监听
  5. 其他常用指令
    • v-text:更新元素的 textContent
    • v-html:更新元素的 innerHTML
    • v-pre:跳过编译
    • v-cloak:隐藏未编译的 Mustache 标签
    • v-once:只渲染一次
    • v-memo(Vue 3.2+):条件性跳过渲染
  6. 自定义指令
    • 注册方式
      • 全局:Vue.directive('focus', {...})
      • 局部:组件中 directives: { focus: {...} }
      • Vue 3 组合式 API:const vFocus = { ... }
    • 指令生命周期
      • Vue 2bindinsertedupdatecomponentUpdatedunbind
      • Vue 3createdbeforeMountmountedbeforeUpdateupdatedbeforeUnmountunmounted
      • 常用钩子:mountedupdatedunmounted
    • 使用场景
      • DOM 操作(如 focus、select)
      • 防抖/节流
      • 权限控制
      • 图片懒加载
      • 拖拽功能
15. 生命周期,mount和create的区别?
  • 在Vue中,created 和 mounted 是两个常用的生命周期钩子函数,它们在组件的生命周期中扮演着不同的角色:
  • created:
    • created 是组件生命周期中的一个钩子函数,在Vue实例被创建后立即调用。
    • 在 created 钩子函数中,Vue实例已经完成了数据观测(data observation),但尚未渲染真实DOM。这意味着你可以访问实例中的数据、方法、计算属性等,但不能保证实例已经被插入到DOM中。
    • created 常用于一些初始化操作,例如数据请求、事件监听或其他非DOM相关的任务。因为此时,组件的模板还未被编译成真实DOM。
  • mounted :
    • mounted 是组件生命周期中的一个钩子函数,在Vue实例挂载到DOM后调用。
    • 在 mounted 钩子函数中,Vue实例已经完成了模板编译,并且已经将生成的虚拟DOM渲染到真实DOM中。
    • mounted 常用于需要对DOM进行操作的任务,例如初始化第三方库、绑定事件监听器、执行动画等。因为此时,组件已经被插入到DOM中,可以安全地访问和操作DOM元素。
  • 区别总结:
    • created 在实例创建后被调用,适合处理数据初始化和非DOM相关的任务。
    • mounted 在实例挂载到DOM后被调用,适合进行DOM操作、初始化第三方库和绑定事件监听。
16. 组件通信
  • 整理 vue 中8种常规的通信方案
  1. 通过 props 传递
  2. 通过 $emit 触发自定义事件 3.使用 ref
  3. EventBus
  4. parentparent 或 root
  5. attrs 与 listeners
  6. Provide 与 Inject
  7. Vuex
17. vuex的理解?
  • Vuex是Vue.js应用程序开发的状态管理模式和库。它为Vue应用程序提供了一个集中式的存储机制,用于管理应用程序的所有组件的状态。Vuex的设计受到了Flux和Redux的影响,它通过以下几个核心概念来工作:
    1. State(状态):应用程序的数据存储在一个单一的状态树中,即 state。这个状态树是响应式的,当状态发生变化时,相关的组件将自动更新。用
    1. Getter (获取器):getter 允许从 state 中派生出一些衍生的状态,类似于计算属性。可以使用 getter 来对 state 进行处理和计算,并将其暴露给组件使用。
    1. Mutation (突变):mutation 是用于修改 state 的唯一途径。它定义了一些操作函数,每个函数都有一个特定的名称(称为 type ),并且可以在这些函数中改变 state 的值。mutation 必须是同步的,以确保状态变更是可追踪的。
    1. Action (动作): action 用于处理异步操作和复杂的业务逻辑。类似于 mutation ,但 action 可以包含异步操作,可以在 action 中触发多个 mutation,也可以在 action 中调用其他 action 。
    1. Module(模块):为了更好地组织和拆分大型的应用程序,Vuex允许将 state、 getter 、mutation 和action 划分为模块。每个模块都有自已的 state、 getter 、 mutation 和 action ,并且可以被嵌套和组合。 通过以上的核心概念,Vuex提供了一种可预测的状态管理方式,使得多个组件之间共享和同步状态变得更加容易和可控。它简化了应用程序的状态管理,提高了代码的可维护性和复用性。
18. get和post的区别?
19. 常见的跨域方式?
20. axios二次封装?
21. async和await?
22. 接口安全?
23. 单项数据流的理解?
24. 对路由的理解?
25. vue如何动态添加属性?
26. Vue中如何做样式穿透?
27. keep-alive 组件缓存,保持状态?
  • 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
  • include和exclude指定是否缓存某些组件
    • include 包含的意思。值为字符串或正则表达式或数组。只有组件的名称与include的值相同的才会被缓存,即指定哪些被缓存,可以指定多个被缓存。
    • exclude相当于include的反义词,就是除了的意思,指定哪些组件不被缓存,用法和include类似
28. token保存在哪里?
29. 自己封装过的常用组件?
30. 打包压缩,dist文件过大,如何处理?
31. vue源码?
32. vue是怎么渲染模板的?

vuetemplate模板编译的过程经过parse()生成ast(抽象语法树),optimize对静态节点优化,generate(生成render字符串之后调用newWatcherO函数,用来监听数据的变化,render函数就是数据监听的回调所调用的,其结果便是重新生成vnode。当这个render函数字符串在第一次mount、或者绑定的数据更新的时候,都会被调用,生成Vnode。如果是数据的更新,那么Vnode会与数据改变之前的Vnode做diff,对内容做改动之后,就会更新到我们真正的DOM

33. diff算法?
  1. diff 算法是一种通过同层的树节点进行比较的高效算法
  2. 其有两个特点
    • 比较只会在同层级进行,不会跨层级比较
    • 在diff比较的过程中,循环从两边向中间靠拢
  3. diff 算法在很多场景下都有应用,在vue 中,主要用于虚拟dom 渲染成真实dom 的新旧 VNode节点比较
  • 具体比较过程

    • 当数据发生改变时, set 方法会调用 Dep.notify 通知所有订阅者 Watcher ,订阅者就会调用 patch 给真实的 Dom 打补丁, 更新相应的视图
    • patch 函数前两个参数位为 oldVnode 和 Vnode ,分别代表之前的旧节点,和新的节点,主要做了四个判断:
      • 没有新节点,直接触发旧节点的 destory 钩子
      • 没有旧节点,说明是页面刚开始初始化的时候,此时,根本不需要比较了,直接全是新建, 所以只调用 createElm
      • 旧节点和新节点自身一样,通过sameVnode 判断节点是否一样,一样时,直接调用patchVnode去处理这两个节点
      • 旧节点和新节点自身不一样,不一样样的时候,直接创建新节点,删除旧节点
    • 那么第三个步骤 patchVnode 做了哪些事呢?
      • 新节点是否是文本节点,如果是,则直接更新 dom 的文本内容为新节点的文本内容
      • 新节点和旧节点如果都有子节点,则处理比较更新子节点
      • 只有新节点有子节点,旧节点没有,那么不用比较了,所有节点都是全新的,所以直接全部新建就好了,新建是指创建出所有新DOM,并且添加进父节点
      • 只有旧节点有子节点而新节点没有,说明更新后的页面,旧节点全部都不见了,那么要做,就是把所有的旧节点删除,也就是直接把 DOM 删除
    • 新节点和旧节点如果都有子节点,则处理比较更新子节点,这块使用 updateChild 方法做比较,处理了五种情况
      • 当新老 VNode 节点的 start 相同时,直接 patchVnode,同时新老 VNode 节点的开始索引都加 1
      • 当新老VNode 节点的 end 相同时,同样直接 patchVnode ,同时新老 VNode 节点的结束索引都减 1
      • 当老 VNode 节点的 start 和新 VNode 节点的 end 相同时,这时候在 patchVnode 后,还需要将当前真实 dom 节点移动到 oldEndVnode 的后面,同时老VNode 节点开始索引I加 1,新 VNode节点的结束索引I减 1
      • 当老 VNode 节点的 end 和新 VNode 节点的 start 相同时, 这时候在patchVnode 后,还需要将当前真实 dom 节点移动到 oldStartVnode 的前面,同时 老 VNode 节点结束索引I减 1,新 VNode 节点的开始索引I加 1
      • 如果都不满足以上情形,那说明没有相同的节点可以复用,则会分为以下两种情况:
        • 从旧的 VNode 为 key 值,对应 index 序列为 value 值的哈希表中找到与newStartVnode 一致 key 的l旧的 VNode 节点,再进行 patchVnode,同时将这个真实 dom 移动到 oldStartVnode 对应的真实 dom 的前面
        • 调用createElm 创建一个新的 dom 节点放到当前newStartIdx 的位置
  • 总结

    • 当数据发生改变时, 订阅者 watcher 就会调用 patch 给真实的 DoM 打补丁
    • isSameVnode 进行判断, 相同则调用 patchVnode 方法
    • patchVnode 做了以下操作:
      • 找到对应的真实 dom ,称为 el
      • 如果都有都有文本节点且不相等,将 el 文本节点设置为 Vnode 的文本节点
      • 如果 oldVnode 有子节点而 VNode 没有, 则删除 el 子节点
      • 如果 oldVnode 没有子节点而 vNode 有, 则将 VNode 的子节点真实化后添加到 el
      • 如果两者都有子节点, 则执行 updateChildren 函数比较子节点
    • updateChildren 主要做了以下操作:
      • 设置新旧 VNode的头尾指针
      • 新旧头尾指针进行比较,循环向中间靠拢,根据情况调用 patchVnode 进行 patch 重复流程、调用 createElem 创建一个新节点, 从哈希表寻找 key 一致的 VNode 节点再分情况操作
34. scoped原理?
  • 在 Vue 中,作用域样式(Scoped Styles)是通过以下原理实现的:
    • 唯一选择器:
      • 当 Vue 编译单文件组件时,在样式中使用 scoped 特性或 module 特性时会为每个样式选择器生成一个唯一的属性选择器。
      • 这里的唯一选择器是类似于[data-v-xxxxxxx] 的属性选择器,其中 xxxxxxx 是一个唯一的标识符。
    • 编译时转换:
      • Vue 在编译过程中会解析单文件组件的模板,并对样式进行处理。
      • 对于具有scoped 特性的样式,Vue 会将选择器转换为带有唯一属性选择器的形式, 例如 .class 会被转换为 .class[data-v-xxxxxxx]。
      • 对于具有 module 特性的样式,Vue 会为每个选择器生成一个唯一的类名,并将类名与元素关联起来。
    • 渲染时应用:
      • 在组件渲染过程中,Vue 会为组件的根元素添加一个属性值为唯一标识符的属性,例如 data-V-xxxxxxx。
      • 当组件渲染完成后,样式选择器中的唯一属性选择器或唯一类名将与组件根元素的属性匹配,从而实现样式的隔离。
      • 这样,只有具有相同属性值的元素才会应用相应的样式,避免了样式冲突和泄漏。
    • 通过以上原理,Vue 实现了作用域样式的隔离。每个组件的样式都被限制在自已的作用域内,不会影响其他组件或全局样式。这种方式实现了组件级别的样式隔离,使得组件可以更好地封装和重用,同时减少了样式冲突的可能性。
35. Echarts常用插件配置?
36. 百度地图?
37. nextTick?

NextTick是什么,在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM,Vue 在更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新

38. toRef 和 toRefs 的区别?
39. query 和 params 的区别?
40. ref 和 reactive 区别?