Skip to content

Vue

MVC、MVP 和 MVVM 模式

在前端开发中,MVC(Model-View-Controller)、MVP(Model-View-Presenter)和 MVVM(Model-View-ViewModel)是常见的架构模式,用于组织和管理应用程序的代码。它们都旨在实现将应用程序的不同部分分离开来,以提高代码的可维护性、可测试性和可扩展性。

  • MVC(Model-View-Controller)

    • 模型(Model):负责应用程序的数据结构、逻辑和数据库交互等。
    • 视图(View):负责应用程序的用户界面呈现,通常由 HTML 和 CSS 组成。
    • 控制器(Controller):充当模型和视图之间的中介,处理用户输入并更新模型和视图。在 Web 应用中,通常是处理路由和 HTTP 请求的部分。

    MVC 模式的核心思想是将应用程序分为三个不同的组件,每个组件有其专注的责任。这样的分离使得代码更易于管理和维护。

  • MVP(Model-View-Presenter)

    • 模型(Model):同 MVC 中的模型,负责应用程序的数据。
    • 视图(View):同 MVC 中的视图,负责用户界面呈现。
    • 主持人(Presenter):取代了 MVC 中的控制器,负责处理用户输入、更新模型并在必要时更新视图。Presenter 的作用是解耦视图和模型,使其更容易进行单元测试。

    MVP 模式在 MVC 的基础上进一步强调了视图和模型之间的分离,并引入了 Presenter 作为中介,使得视图更加 passively 与模型交互。

  • MVVM(Model-View-ViewModel)

    • 模型(Model):同 MVC 和 MVP 中的模型,负责应用程序的数据。
    • 视图(View):同 MVC 和 MVP 中的视图,负责用户界面呈现。
    • 视图模型(ViewModel):MVVM 中最为核心的部分。视图模型是视图的抽象,负责从模型中获取数据并准备视图所需的数据。视图模型通常是纯粹的 JavaScript 对象,它实现了视图和模型之间的数据绑定。

    MVVM 模式相比 MVC 和 MVP 更进一步,它引入了视图模型的概念,通过数据绑定实现了视图和模型之间的双向绑定。这样,当模型发生变化时,视图会自动更新,反之亦然。

观察者模式和发布订阅模式

  • 发布订阅模式其实属于广义上的观察者模式。

  • 在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。

  • 而在发布订阅模式中,发布者和订阅者之间多了一个调度中心。调度中心一方面从发布者接收事件,另一方面向订阅者发布事件,订阅者需要在调度中心中订阅事件。通过调度中心实现了发布者和订阅者关系的解耦。使用发布订阅者模式更利于我们代码的可维护性。

Vue 双向数据绑定原理

Vue.js 中的双向数据绑定是其最显著的特性之一,它允许视图层(HTML)和数据模型之间的自动同步。这意味着当数据模型发生变化时,视图会自动更新,并且当用户在视图中输入内容时,数据模型也会相应地更新

实现双向数据绑定的原理如下:

  • 数据劫持(Data Observation): Vue.js 使用了一个名为“响应式系统(Reactivity System)”的机制来实现数据劫持。当你创建一个 Vue 实例时,Vue 会遍历数据对象的所有属性,使用 Object.defineProperty() 方法将它们转换为 getter 和 setter。通过这种方式,当属性被访问或修改时,Vue 能够捕获到,并执行相应的操作。

    在 Vue.js 2 中 使用 Object.defineProperty() 来进行数据劫持,有一些对属性的操作无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,Vue.js 2 内部通过重写函数解决了这个问题。

    在 Vue.js 3 中,数据劫持的实现方式与 Vue.js 2 有所不同,主要通过 Proxy 对象来实现。

    • Proxy 对象
      在 Vue.js 3 中,使用了 ES6 中的 Proxy 对象来替代 Object.defineProperty()。Proxy 对象允许你创建一个代理,用于定义基本操作的自定义行为(例如属性查找、赋值等)。通过 Proxy 对象,Vue.js 能够劫持数据对象的操作,并在数据发生变化时进行相应的处理。

    • Reflect API
      为了与 Proxy 对象配合使用,Vue.js 3 还使用了 Reflect API。Reflect API 提供了一组用于操作对象的方法,例如 Reflect.get()Reflect.set() 等。Vue.js 3 在数据劫持时使用 Reflect API 来执行实际的操作,而不是直接在目标对象上操作,这样可以更加灵活地处理各种情况。

    • 优势

      • Proxy 对象更加强大和灵活,可以监听更多类型的操作,例如数组的变化。
      • Proxy 对象可以监听整个对象的变化,而不仅仅是属性的变化。
      • Proxy 对象的性能通常比 Object.defineProperty() 更好,特别是在大型数据对象上的性能表现更佳。
  • 虚拟 DOM(Virtual DOM): Vue.js 使用虚拟 DOM 来提高性能。虚拟 DOM 是一个 JavaScript 对象,它对真实 DOM 进行抽象,然后在内存中对其进行操作。当数据发生变化时,Vue.js 会生成一个新的虚拟 DOM,并与之前的虚拟 DOM 进行比较,找出变化的部分,然后只更新变化的部分到真实 DOM 中,而不是重新渲染整个页面。

  • 事件监听(Event Listening): Vue.js 会在模板编译时,为每个模板中的指令(例如 v-model)或事件绑定(例如 @input)创建对应的监听器。当用户输入内容时,Vue.js 会通过这些监听器捕获到事件,并更新数据模型。

  • 数据绑定(Data Binding): 在 Vue.js 中,你可以使用 v-model 指令来实现双向数据绑定。v-model 指令在表单元素上创建双向数据绑定,将表单元素的值绑定到数据模型上,并且当表单元素的值发生变化时,数据模型也会更新。

综上所述,Vue.js 的双向数据绑定是通过数据劫持虚拟 DOM事件监听数据绑定等机制来实现的。这些机制使得数据模型与视图之间的同步变得简单高效,极大地提高了开发效率。

虚拟 DOM(Virtual DOM)

虚拟 DOM 是一个 JavaScript 对象,它是对真实 DOM 的抽象。通过使用虚拟 DOM,Vue.js 能够在内存中维护一颗以 JavaScript 对象为节点的树,这颗树与真实 DOM 对应着页面的结构。

当数据发生变化时,Vue.js 首先会生成一个新的虚拟 DOM 树,然后通过 diff 算法比较新的虚拟 DOM 树与旧的虚拟 DOM 树的差异。通过比较,Vue.js 找出需要更新的部分,并只更新这些部分到真实 DOM 上,而不是重新渲染整个页面。这样可以大大提高页面的渲染性能和响应速度。

优势

  • 提高性能:由于操作虚拟 DOM 可以避免直接操作真实 DOM,减少了浏览器的重绘和回流,从而提高了页面的渲染性能。
  • 简化操作:通过使用虚拟 DOM,开发者可以像操作普通 JavaScript 对象一样操作 DOM,简化了操作复杂 DOM 结构的过程。
  • 跨平台兼容性:虚拟 DOM 不依赖于具体的浏览器平台,因此可以在不同的平台上运行,提高了跨平台兼容性。

diff 算法

在 Vue.js 中,diff 算法是用于比较虚拟 DOM 树的变化,并最小化 DOM 操作的一种算法。这种算法可以高效地找出虚拟 DOM 树中变化的部分,并只更新这些部分到真实 DOM,而不是重新渲染整个页面。

  • 概念
    diff 算法是一种通过比较两棵树的差异,找出最小的变更,并应用到真实 DOM 上的技术。在 Vue.js 中,这两棵树分别是当前虚拟 DOM 树和更新后的虚拟 DOM 树。

  • 算法流程

    • 深度优先遍历:Vue.js 使用深度优先遍历算法来逐层比较虚拟 DOM 树的节点。
    • 同级比较:在遍历过程中,Vue.js 会比较同级节点之间的差异,判断节点是否需要更新、新增或删除。
    • 唯一 key:在比较过程中,Vue.js 会使用节点的唯一 key 来确定节点的身份,以确保正确地识别出节点的变化。
    • 更新策略:根据节点的变化类型(新增、删除、移动、更新等),Vue.js 会采取不同的更新策略,最大程度地减少 DOM 操作的次数。
    • 递归处理:如果节点发生变化,则递归处理其子节点,直到完成整个树的比较。
  • 优化策略

    • Key 的重要性:使用唯一 key 可以帮助 Vue.js 更准确地识别节点的变化,提高 diff 算法的效率。
    • 移动操作的优化:在节点移动时,Vue.js 会尽可能地复用已存在的 DOM 元素,而不是直接删除和重新创建,以减少 DOM 操作。
    • 批量处理操作:Vue.js 会将多个变更操作合并为一个批量操作,减少了对真实 DOM 的操作次数,提高了性能。
  • 时间复杂度
    Vue.js 中的 diff 算法的时间复杂度为 O(n),其中 n 表示节点的数量。这是由于 diff 算法会遍历整个虚拟 DOM 树,并比较节点之间的差异,因此其时间复杂度与节点的数量成正比。

Vue 2 的生命周期

Vue 的生命周期指的是组件从创建到销毁的一系列的过程,一共有 8 个阶段,分别是创建前、创建后、挂载前、挂载后、更新前、更新后、销毁前和销毁后,每个阶段对应了一个生命周期的钩子函数。开发者可以在这些钩子函数中执行自定义的操作。

  • 创建阶段(Creation)

    • beforeCreate:在实例初始化之后、数据观测(data observation)和 event/watcher 事件配置之前被调用。此时组件实例还未初始化,因此无法访问到 data 和 methods 等选项。
    • created:在实例创建完成后被立即调用。这个阶段完成了数据的观测、属性和方法的运算,但是尚未挂载到 DOM 上。
  • 挂载阶段(Mounting)

    • beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
    • mounted:在挂载结束后被调用:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用此钩子函数。此时组件已经挂载到 DOM 上,可以访问到 DOM 元素。(请求数据)
  • 更新阶段(Updating)

    • beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在该钩子中对数据进行修改,但是不会触发附加的重新渲染过程。
    • updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁后调用。此时组件的 DOM 已经更新完毕,可以执行依赖于 DOM 的操作。
  • 销毁阶段(Destruction)

    • beforeDestroy:在实例销毁之前调用。在这一步,实例仍然完全可用。(清除定时器、解绑全局事件)
    • destroyed:在实例销毁之后调用。此时组件已经从 DOM 中移除,所有的事件监听器和子实例也都被销毁。
  • 错误处理阶段(Error Handling)

    • errorCaptured:当捕获一个来自子孙组件的错误时被调用。可以返回 false 以阻止该错误继续向上传播。

当使用 keep-alive 组件的时候,还有两个钩子函数,分别是 activateddeactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

Vue 3 的生命周期

Vue 3 的生命周期与 Vue 2 相比有了一些变化。在 Vue 3 中,生命周期钩子被分为两类:

  • 选项式 API 生命周期钩子:这些钩子与 Vue 2 中的生命周期钩子相同,但它们是作为选项传递给 Vue 组件的。
  • 组合式 API 生命周期钩子:这些钩子是新的,它们允许你在组件的任何地方使用它们。

选项式 API 生命周期钩子

  • beforeCreate:在组件实例被创建之前被调用。
  • created:在组件实例被创建之后被调用。
  • beforeMount:在组件实例被挂载到 DOM 之前被调用。
  • mounted:在组件实例被挂载到 DOM 之后被调用。
  • beforeUpdate:在组件实例更新之前被调用。
  • updated:在组件实例更新之后被调用。
  • beforeDestroy:在组件实例被销毁之前被调用。
  • destroyed:在组件实例被销毁之后被调用。

组合式 API 生命周期钩子

  • onBeforeMount:与 beforeMount 钩子相同。
  • onMounted:与 mounted 钩子相同。
  • onBeforeUpdate:与 beforeUpdate 钩子相同。
  • onUpdated:与 updated 钩子相同。
  • onBeforeUnmount:与 beforeDestroy 钩子相同。
  • onUnmounted:与 destroyed 钩子相同。
  • onErrorCaptured:在组件中捕获到错误时被调用。
  • onRenderTracked:在组件的渲染被追踪时被调用。

生命周期钩子的使用

以下是一些使用生命周期钩子的示例:

  • beforeCreate 钩子中初始化数据
  • mounted 钩子中订阅数据变化
  • beforeUpdate 钩子中执行 DOM 操作
  • updated 钩子中进行网络请求
  • onErrorCaptured 钩子中处理错误

keep-alive 组件

<keep-alive> 是 Vue.js 中的一个内置组件,用于保留组件状态或避免重新渲染。它可以将动态组件包裹起来,并缓存不活动的组件实例,而不会销毁它们。当组件再次被访问时,不需要重新创建,而是直接从缓存中取出并重新渲染,从而提高了性能和用户体验。

下面是 <keep-alive> 组件的主要特性和用法:

  • 特性

    • 包裹动态组件<keep-alive> 主要用于包裹动态组件,可以是通过 <component> 标签动态渲染的组件,也可以是通过路由动态加载的组件。
    • 缓存不活动组件:一旦组件被包裹在 <keep-alive> 内部,当组件不活动时(例如被切换到其他页面),它将被缓存起来而不是被销毁,从而保留其状态。
    • 激活钩子<keep-alive> 组件提供了两个钩子函数 activateddeactivated,用于在组件被激活和停用时进行操作。
  • 用法

    html
    <keep-alive>
      <component :is="currentComponent"></component>
    </keep-alive>

    在上面的示例中,<component> 标签渲染的组件将被 <keep-alive> 组件包裹起来,从而实现了组件的缓存。currentComponent 是一个动态绑定的组件名称或组件对象,根据实际需求动态切换不同的组件。

  • 钩子函数

    • activated:在组件被激活时调用,即组件被插入到 DOM 中并进行显示。
    • deactivated:在组件被停用时调用,即组件被从 DOM 中移除并隐藏起来。

使用 <keep-alive> 组件可以有效地提高页面性能,特别是在大型单页应用中,当页面切换频繁时能够减少不必要的组件销毁和重新创建,从而加快页面加载速度和提升用户体验。

Vue 组件通信

  • Props / Emit(父子组件通信)

    • Props:父组件通过 props 向子组件传递数据。子组件通过 props 接收数据,并根据传递的数据进行渲染。
    • Emit:子组件通过 $emit 方法触发事件,将需要传递给父组件的数据作为参数传递给父组件。父组件通过监听子组件的事件,从而获取子组件传递的数据。
  • Provide / Inject(祖先组件向后代组件传递数据)

    • Provide:祖先组件通过 provide 选项向后代组件提供数据。提供的数据可以是基本数据类型或对象,后代组件通过 inject 选项接收提供的数据。
    • Inject:后代组件通过 inject 选项接收来自祖先组件提供的数据。
  • $parent / $children

    • $parent 属性可以让子组件访问其父组件。
    • $children 属性可以让父组件访问其所有子组件。
  • $attrs / $listeners(透传属性和事件)

    • $attrs:父组件通过 v-bind="$attrs" 将属性透传给子组件,子组件可以通过 $attrs 获取父组件传递的属性。
    • $listeners:父组件通过 v-on="$listeners" 将事件透传给子组件,子组件可以通过 $listeners 获取父组件传递的事件。
  • Ref / Refs

    • Ref:父组件通过 ref 属性获取子组件的引用,从而调用子组件中的方法或访问子组件的属性。
    • Refs:在 Vue 3 中,可以通过 ref 函数创建一个响应式的引用,并通过 ref 绑定到子组件中,从而实现对子组件的访问和操作。
  • Event Bus(事件总线)

    • 创建一个全局的 Vue 实例作为事件总线,在组件中通过该实例进行事件的发布和订阅,实现组件之间的通信。但是,事件总线在大型应用中会导致事件的管理和维护变得复杂,因此在小型应用中使用较为合适。
  • Vuex / Pinia(状态管理)
    Vuex 和 Pinia 是 Vue.js 的官方状态管理库,用来实现全局状态管理,从而实现任意组件之间的数据共享。

computed 和 watch

在 Vue.js 中,computedwatch 是用于监听数据变化并执行相应逻辑的两种重要属性。它们都与响应式数据绑定相关,但用途和使用方式略有不同。

Computed(计算属性)

computed 是一个 Vue 实例的属性,用于声明一个计算属性。它的值是一个函数,该函数可以返回计算后的值,Vue.js 会自动将其缓存,只有在依赖的响应式数据发生改变时才会重新计算。computed 主要用于派生出一个新的数据,以供模板中使用,它具有以下特点:

  • 自动缓存:当依赖的响应式数据没有发生变化时,computed 属性会直接返回上一次的计算结果,而不会重新执行计算函数。
  • 懒计算:只有当 computed 属性在模板中被引用时,计算函数才会被执行,这样可以避免不必要的计算。
  • 响应式computed 属性会自动跟踪依赖的响应式数据,当这些数据发生变化时,computed 会重新计算其值。
javascript
// 示例
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName;
  }
}

Watch(监听器)

watch 是一个 Vue 实例的选项,用于监听一个指定的数据,并在该数据变化时执行特定的逻辑。它的值可以是一个函数或者一个对象,对象中的属性对应着需要监听的数据,值为回调函数。watch 主要用于监听数据的变化并执行异步或复杂的操作,它具有以下特点:

  • 可监听对象和深度监听:可以监听对象和数组的变化,并通过设置 deep: true 进行深度监听。
  • 更灵活的操作:可以执行一些比较复杂的操作,例如异步请求、定时器等。
  • 监听特定属性:可以通过字符串形式或函数形式指定需要监听的属性。
javascript
// 示例
watch: {
  firstName(newVal, oldVal) {
    console.log('firstName 改变了');
  },
  'obj.prop': {
    handler(newVal, oldVal) {
      console.log('obj.prop 改变了');
    },
    deep: true
  }
}

区别和适用场景

  • computed 适用于派生出一个新的数据,通常是依赖于一个或多个响应式数据计算而来的数据,它具有自动缓存和懒计算的特性,适合用于模板中频繁使用的数据。
  • watch 适用于监听一个指定的数据的变化,并执行一些复杂的逻辑,例如异步操作、定时器等。适合处理一些需要特定操作的情况,比如数据变化时发起网络请求等。

vue-router 中的钩子函数

Vue Router 中的钩子函数是用于在路由导航过程中执行特定逻辑的函数。它们允许您在路由发生变化前或后执行某些操作,例如验证导航、加载数据、修改页面标题等。

  • 全局前置守卫

    • beforeEach(to, from, next):在路由跳转之前被调用,可以用于进行全局的路由导航守卫逻辑。例如,验证用户是否登录或是否有权限访问该路由。
    • afterEach(to, from):在路由跳转完成之后被调用,无论是跳转到相同的路由还是跳转到不同的路由。
    • beforeResolve(to, from, next):在路由跳转之前被调用,但在路由组件内的 beforeRouteEnter 守卫之前调用。用于确保所有异步路由组件都被解析。
  • 路由独享守卫

    • beforeEnter(to, from, next):在路由独享守卫中定义,只在该路由生效。在路由被激活时调用,可用于对特定路由进行验证或逻辑处理。
  • 组件内的守卫

    • beforeRouteEnter(to, from, next):在路由进入该组件前被调用,此时组件实例尚未创建,因此无法访问 this。可以通过 next(vm => {}) 获取组件实例。
    • beforeRouteUpdate(to, from, next):在当前路由改变,但是该组件被复用时被调用。例如,从 /user/1 导航到 /user/2 时,同一个组件实例被复用。
    • beforeRouteLeave(to, from, next):在离开该路由前被调用,可以用于在离开当前页面时执行一些清理操作或提醒用户。如果确认离开,需要调用 next(),否则可以调用 next(false) 或者传递一个路由对象来重新定向。

$route 和 $router

  • $route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。
  • $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。

Vue 修饰符

在 Vue.js 中,修饰符是用于改变指令行为或者监听器行为的特殊标记,它们可以与指令一起使用,以便更灵活地控制指令的行为。以下是常用的 Vue 修饰符及其作用:

  • v-on 修饰符

    • .stop:阻止事件继续传播。
    • .prevent:阻止事件的默认行为。
    • .capture:事件捕获模式,即在捕获阶段处理事件。
    • .self:当事件是从触发元素自身触发时才触发回调。
    • .once:只触发一次事件处理器,之后移除。
    • .passive:告诉浏览器不要阻止默认事件的默认行为,用于提高滚动性能。
  • v-bind 修饰符

    • .prop:用于绑定 DOM 属性。
    • .camel:将 kebab-case 特性名转换为 camelCase。
    • .sync:用于实现子组件和父组件之间双向数据绑定。
  • v-model 修饰符

    • .lazy:在 input 事件中同步改变数据而不是在 change 事件中。
    • .number:将输入值转为数值类型。
    • .trim:自动过滤输入的首尾空白字符。
  • v-show 修饰符

    • .sync:用于实现子组件和父组件之间双向数据绑定。
  • v-text 和 v-html 修饰符

    • .once:只渲染一次,之后不再更新。
  • v-slot 修饰符

    • #default:默认插槽。
    • #name:具名插槽。
  • 事件修饰符

    • .stop:阻止事件继续传播。
    • .prevent:阻止事件的默认行为。
    • .capture:事件捕获模式。
    • .self:当事件是从触发元素自身触发时才触发回调。
    • .once:只触发一次事件处理器。
    • .passive:告诉浏览器不要阻止默认事件的默认行为。

Vue 中 key 值的作用

在 Vue.js 中,key 是用于帮助 Vue 识别 VDOM 中的每个节点的特殊属性。当 Vue 用 v-for 正在更新已渲染过的元素列表时,它默认使用“就地复用”策略。如果数据项的顺序被改变,Vue 将不是移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

然而,这种默认的模式会导致一个问题:当一个数组中的某些元素的标识(即 key 值)发生改变时,Vue 不会知道哪个元素被修改了,导致页面的错误渲染。

因此,为了给 Vue 提供一个提示,以便它跟踪每个节点的身份,从而重用和重新排序现有元素,需要为每项提供一个唯一的 key 值。这个 key 值需要是每项数据在列表中唯一的标识符,通常是数据中的某个唯一值,比如数据项的 ID。

key 值的作用主要有以下几个方面:

  1. 跟踪节点身份:Vue 使用 key 属性来识别节点的身份,确保在数据改变时能够正确地重用和重新排序现有元素,而不会导致错误的渲染。
  2. 优化性能:通过正确使用 key 属性,可以提高 Vue 的性能,减少不必要的 DOM 操作,尤其是在处理大型列表或频繁更新的情况下。
  3. 避免重复渲染:使用 key 属性能够避免相同的数据项被重复渲染,确保每个节点的状态正确。

nextTick

在 Vue 中,nextTick 是一个异步方法,用于在 DOM 更新后执行回调函数。当你修改了 Vue 实例中的数据,Vue 并不会立即更新 DOM。相反,它会等待数据变化完成,然后批量异步更新 DOM。在这种情况下,如果你想在 DOM 更新完成后执行一些操作,就可以使用 nextTick 方法。

为什么 v-for 和 v-if 不建议结合使用

  1. 性能问题:当 v-forv-if 结合使用时,Vue 将会为每个循环项都执行 v-if 条件判断。这意味着在每次渲染循环时,都会对条件进行判断,可能会导致不必要的性能开销,特别是在循环中有大量数据时。这可能会使页面变得很慢。

  2. 可读性问题:当 v-forv-if 结合使用时,代码可能变得更难理解和维护。因为 v-if 可能会隐藏或显示 v-for 中的某些项目,这会使模板的逻辑变得复杂。

  3. 潜在的 bug:在某些情况下,v-ifv-for 结合使用可能会导致意外行为。例如,当 v-if 控制的元素在 v-for 循环中被移动或重新排序时,可能会导致渲染结果不符合预期。

为了避免这些问题,Vue 官方文档建议尽量避免在同一个元素上同时使用 v-forv-if。可以通过以下几种方法来解决:

  • 计算属性:将条件逻辑移至计算属性中,在模板中只使用 v-for。这样可以使模板更简洁,也更易于理解和维护。

  • 过滤数据:在数据层面过滤掉不需要的项,然后在模板中只使用 v-for 渲染。这样可以避免在模板中进行条件判断,提高性能。

  • 使用 <template> 元素:在某些情况下,你可能需要将 v-if 放在包裹元素上,而不是在循环的元素上。可以使用 <template> 元素作为容器来实现这一点。

    html
    <ul>
      <template v-for="user in users" :key="user.id">
        <li v-if="user.isActive">{{ user.name }}</li>
      </template>
    </ul>

Vue 2 和 Vue 3 的区别

  • Typescript 支持

    • Vue 3 对 TypeScript 的支持更加完善。Vue 3 的源码是使用 TypeScript 编写的,并且提供了更好的类型推断和类型支持,使得开发者可以更轻松地使用 TypeScript 来开发 Vue 应用。
  • API 设计

    • Vue 3 引入了 Composition API(组合 API),这是一个全新的 API 设计,使得组件的逻辑更加模块化。相比 Vue 2 中的 Options API(选项 API),Composition API 提供了更灵活的组织代码的方式,特别适合于大型应用程序的开发。
  • 响应式原理

    • 在 Vue.js 2 中 使用 Object.defineProperty() 来进行数据劫持,有一些对属性的操作无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,Vue.js 2 内部通过重写函数解决了这个问题。
    • 在 Vue.js 3 中,使用了 ES6 中的 Proxy 对象来替代 Object.defineProperty()
  • 生命周期变化

    Vue 2Vue 3
    beforeCreatesetup() 替代
    createdsetup() 替代
    beforeMountonBeforeMount
    mountedonMounted
    beforeUpdateonBeforeUpdate
    updatedonUpdated
    beforeDestroyonBeforeUnmount
    destroyedonUnmounted
  • 性能提升

    特性Vue 2Vue 3
    打包体积全量引入(即使不用某些功能)Tree-shaking(按需引入)
    虚拟 DOM全量对比差异静态标记 + 动态追踪(diff 更快)
    SSR 性能一般提升 2~3 倍
  • 新增特性

    Vue 3 新增说明
    Teleport将组件渲染到 DOM 任意位置(如弹窗)
    Suspense异步组件加载的等待状态处理
    Fragment支持组件多根节点(无需外层 div)
    多个 v-model同一组件支持多个 v-model 绑定
  • Tree-shaking 改进

    • Vue 3 在设计上更加友好于 Tree-shaking,在构建过程中能够更轻松地剔除未使用的代码,减少最终的包大小。
  • 全局 API 的修改

    • Vue 3 对一些全局 API 进行了修改。例如,Vue 3 中的 Vue.observable() 替代了 Vue 2 中的 Vue.set()Vue.delete(),使得在响应式对象上进行操作更加简洁明了。

Vue 性能优化方案

组件优化

  • 按需加载(懒加载):使用 defineAsyncComponentimport() 进行异步组件加载,减少首屏加载时间。
  • 避免不必要的组件重渲染:使用 v-once(静态内容)、v-memo(缓存计算)、computed 进行依赖追踪,减少渲染开销。
  • 使用 key 提高 Diff 算法效率v-for 遍历时指定唯一 key,减少 DOM 操作。

数据响应优化

  • 合理使用 computed 代替 methods:避免重复计算,提高性能。
  • 避免深度监听:使用 immediate: false,或监听具体属性而不是整个对象。
  • 使用 shallowRef / shallowReactive:减少 Vue 深层递归监听,适用于浅层数据变化。

路由优化

  • 路由懒加载:使用 defineAsyncComponent()() => import('xxx.vue') 进行按需加载。
  • 路由缓存(keep-alive):缓存页面组件,避免重复渲染,适用于多次切换的页面。

事件与 DOM 优化

  • 使用事件委托:减少事件绑定,适用于列表或动态元素(@click.stop 仅阻止当前元素)。
  • 减少 DOM 操作:尽量通过 Vue 的响应式数据驱动 UI 变化,而非 document.querySelector() 手动操作 DOM。
  • 使用虚拟滚动(Virtual Scrolling):处理大数据列表(如 Vue Virtual Scroller)。

网络与静态资源优化

  • 开启 gzip 压缩:通过 Webpack compression-webpack-plugin 或 Nginx 配置压缩。
  • 使用 CDN 加速:将 Vue、Element-UI 等静态资源放入 CDN。
  • 按需加载 UI 组件库:如 import { Button } from 'element-plus',避免全量引入。
  • 图片优化:使用 webp 格式、懒加载(v-lazy)、雪碧图(sprite)等。

Webpack/Vite 构建优化

  • Tree Shaking:确保 sideEffects: false,去除未使用代码。
  • 代码分割(Code Splitting)splitChunks 拆分代码,减少主包体积。
  • 开启缓存:使用 cache-loader,减少二次打包时间。
  • 使用 Vite:更快的 HMR(热更新),优化开发体验。

SSR / 静态生成

  • 使用 Nuxt.js 进行 SSR 或静态生成(SSG):提升首屏加载速度,优化 SEO。
  • 开启预渲染:对于小型项目,使用 prerender-spa-plugin 生成静态 HTML。

Released under the AGPL-3.0 License