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 2 使用 Object.defineProperty

    • 递归遍历 data 对象属性
    • 通过 getter 收集依赖(Watcher)
    • 通过 setter 触发更新(Dep.notify)
    • 缺点:无法检测对象新增/删除属性、通过下标方式修改数组数据,Vue 内部通过重写函数解决了这个问题。
  • Vue 3 改用 ES6 中的 Proxy

    1. 直接代理整个对象(无需递归初始化)
    2. 支持动态新增/删除属性检测
    3. 性能更好,可拦截 13 种操作
    4. 通过 Reflect 操作原始数据

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 有所不同,通过使用 ES6 中的 Proxy 对象来替代 Object.defineProperty()。Proxy 对象允许创建一个代理,用于监听对象的各种变化。通过 Proxy 对象,Vue.js 能够劫持数据对象的操作,并在数据发生变化时进行相应的处理。为了与 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 上,而不用重新渲染整个页面。这样可以大大提高页面的渲染性能和响应速度。

优势

  • 提高性能:避免直接操作真实 DOM,减少浏览器的重绘和重排(回流)。
  • 简化操作:使用虚拟 DOM 可以像操作普通 JavaScript 对象一样操作 DOM。
  • 跨平台兼容性:虚拟 DOM 不依赖于具体的浏览器平台,可以在不同的平台上运行,提高了跨平台兼容性。

diff 算法

在 Vue.js 中,diff 算法是通过比较新旧虚拟 DOM 树的差异,找出最小的变更,并且只更新这些部分到真实 DOM 上,而不用重新渲染整个页面。

  • 算法流程

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

    • 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> 主要用于包裹动态组件,可以是通过 <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(Vue 2)

    • $parent 属性可以让子组件访问其父组件。
    • $children 属性可以让父组件访问其所有子组件。
  • 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 会重新计算其值。
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 识别虚拟 DOM 中的每个节点的特殊属性。当 Vue 用 v-for 正在更新已渲染过的元素列表时,它默认使用“就地复用”策略。如果数据项的顺序被改变,Vue 将不是移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

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

因此,为了给 Vue 提供一个提示,以便它跟踪每个节点的身份,从而重用和重新排序现有元素,需要为每项提供一个唯一的 key 值。

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 3 Composition API 设计优势

  • 对比 Options API:

    1. 逻辑复用更强:自定义 Hook 替代 Mixins
    2. 代码组织更灵活:按功能聚合代码(不再分散在 data/methods)
    3. 类型推导更强:更好的 TypeScript 支持
    4. Tree-shaking:按需引入 API
    5. 函数式编程思想
  • 核心 API: ref/reactive、computed、watch、provide/inject

Vue 2 和 Vue 3 的区别

  • TypeScript 支持

    • Vue 3 的源码是使用 TypeScript 编写的,对 TypeScript 的支持更加完善,提供了更好的类型推断和类型支持。
  • API 设计

    • Vue 3 引入了 Composition 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 操作。

运行时优化

  • 避免 v-ifv-for 同时使用
  • 使用 keep-alive 缓存组件
  • 防抖节流高频操作

数据响应优化

  • 合理使用 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。

Vuex 与 Pinia 架构差异

Vuex 核心概念

  • State / Getters / Mutations / Actions / Modules

Pinia 改进

  1. 去除 mutations(直接通过 actions 修改状态)
  2. 完善的 TypeScript 支持
  3. 组合式 API 风格(类似 setup())
  4. 自动代码分割(无需动态注册模块)
  5. 更简洁的 API 设计(createStore())
  6. 支持 Vue DevTools 时间旅行调试

Vue 服务端渲染(SSR)实现原理

  1. 核心流程

    • Node.js 服务器接收请求
    • 创建 Vue 实例并渲染成 HTML 字符串
    • 将 HTML 与客户端激活脚本(hydration)一起返回
  2. Nuxt.js 优化

    • 自动代码分割
    • 异步数据获取(asyncData)
    • 静态站点生成(SSG)
    • 中间件机制
  3. 注意事项

    • 避免全局单例(需工厂函数创建实例)
    • 生命周期差异(无 mounted 等浏览器钩子)
    • 数据预取与状态同步

设计可复用组件需考虑哪些因素?

  1. 接口设计

    • Props 类型校验(TypeScript)
    • 合理的默认值
    • 支持 v-model 双向绑定
  2. 扩展性

    • 使用插槽提供定制能力
    • 提供 CSS 作用域(scoped/ CSS Modules)
    • 支持自定义类名和样式
  3. 兼容性

    • 处理浏览器差异
    • 响应式布局适配
    • 无障碍访问(ARIA)
  4. 文档与示例

    • 清晰的 API 文档
    • Storybook 可视化示例
    • 提供 Playground 演示
  5. 性能优化

    • 延迟加载非关键内容
    • 避免不必要的重新渲染
    • 使用虚拟滚动优化长列表

Released under the AGPL-3.0 License