vue 原理高频面试题汇总

131 阅读13分钟

一、谈谈对vue的理解

Vue 是数据驱动视图的 JavaScript 前端视图层框架,核心特点如下:

  • 声明式渲染:通过模板语法声明数据与视图的绑定关系,无需手动操作 DOM 即可实现渲染。

  • 响应式数据绑定:自动追踪数据变化,数据更新时,框架自动同步更新关联视图。

  • 虚拟 DOM 优化:用 JavaScript 对象模拟真实 DOM,数据变化时通过 Diff 算法对比新旧虚拟 DOM,仅更新差异部分,实现批量高效更新。

  • 组件化开发:以可复用的 Vue 实例构建组件,支持嵌套使用;组件间通过特定机制通信,数据变更时,基于响应式与虚拟 DOM 完成实例更新。

二、vue2/vue3 的响应式原理

1、vue2响应式原理

Vue 2 的响应式原理的核心是要理解 getter/setterWatcher 类(即副作用函数)、Dep 类(存储依赖)之间的关系。

  • 核心概念:Vue 2 利用 Object.defineProperty() 把对象属性转换为 getter/setter,进行数据劫持。Object.defineProperty() 是 ES5 提供的一个方法,基本语法为 Object.defineProperty(obj, prop, descriptor),通过它可以对对象属性的读写操作进行拦截。

  • 依赖收集:在组件渲染过程中,会创建 Watcher 实例并将其设置为当前激活的副作用函数。当访问对象属性值时,会触发属性的 getter 方法,此时会将当前激活的副作用函数(即 Watcher 实例)存储在 Dep 类的 subs 数组中。

  • 触发更新:修改对象属性值时会触发属性的 setter 方法,调用该属性对应的 Dep 实例的 notify 方法。notify 方法会遍历 subs 数组,并调用每一个 Watcher 实例的 update 方法,update 方法执行后可能会重新渲染组件,以保证视图与数据的一致性。

  • 数组处理Object.defineProperty() 无法劫持数组的所有方法,Vue 2 通过继承数组原型,对 pushpopshiftunshiftsplicesortreverse 等方法进行重写。在重写的方法中,会先调用原始数组方法,然后手动触发更新,从而实现数组的响应式更新。

  • 局限性

    • Object.defineProperty 主要监听对象属性值的变化,不能监听对象属性的新增和删除。在实际开发中,需要使用 Vue.set() 和 Vue.delete() 方法来处理。例如,当我们要给一个响应式对象新增一个属性时,可以使用 Vue.set(obj, 'newProp', 'newValue');删除属性时可以使用 Vue.delete(obj, 'prop')
    • Vue 2 中对于嵌套层级较深的响应式对象,初始化过程中就会逐层进行数据劫持,这可能会导致性能问题,尤其是在处理大型复杂数据结构时。

2、vue3响应式原理

Vue3响应式原理和vue2大体一致,都是在数据读取时收集正在执行的副作用函数作为依赖,在数据修改时触发依赖集合里、的副作用函数,进行更新。

不同点

  • vue2 通过 Object.defineProperty 进行数据劫持,而 vue3 通过 Proxy 代理对象进行劫持。前者主要监听对象属性值的变化,不能监听对象属性的新增和删除,后者基于整个对象的监听,不存在上述问题。

  • vue2 中对于嵌套层级较深的响应式对象,初始化过程中就会逐层进行数据劫持,而vue3 采用了懒劫持的策略,访问到的时候才会进行数据劫持。

  • vue2 中Object.defineProperty() 无法劫持数组的所有方法,所以需要手动对 pushpopshiftunshiftsplicesortreverse 等方法进行重写,以实现响应式更新。Vue 3 借助 Proxy 强大的拦截能力,对数组的各种操作实现了更全面、更自然的响应式处理,避免了 Vue 2 中对数组方法重写的复杂性。

三、虚拟dom 和 deef 算法的理解

虚拟dom

页面的所有元素、属性和文本都通过 DOM 节点表示, VDOM(Virtual DOM,虚拟 DOM) 是DOM渲染的一种优化,它是一个内存中的虚拟树,是真实 DOM 的轻量级 JavaScript 对象表示。

VDOM主要用于优化 UI 渲染性能,它的工作流程大致如下:

  • 1️⃣创建虚拟 DOM:当组件的状态或数据发生变化时,Vue 会重新生成虚拟 DOM。
  • 2️⃣比较虚拟 DOM 和真实 DOM:Vue 使用一种高效的算法来比较新旧虚拟 DOM 的差异(即 diff 算法)。
  • 3️⃣更新 DOM:根据差异更新真实的 DOM,仅修改有变化的部分,而不是重新渲染整个 DOM 树。

diff算法

Vue的diff算法执行,依赖数据的的响应式系统:当数据发生改变时, setter 方法会让调用 Dep.notify 通知所有订阅者 Watcher ,订阅者会重新执行渲染函数,渲染函数内部通过diff 算法用于比较新旧虚拟 DOM 树的差异,并计算出最小的更新操作,最终更新相应的视图。

image.png

diff 算法的核心算法流程如下:

  • 节点对比 如果新旧节点类型相同,则继续比较它们的属性。如果节点类型不同(如元素和文本节点不同),则直接替换整个节点。
  • 属性更新: 如果节点类型相同,接下来检查节点的属性。对于不同的属性值进行更新,移除旧属性,添加新属性。
  • 子节点比对: 对于有子节点的元素(如 div),Vue 会使用不同的策略来优化子节点更新:
  • 🎯 文本节点的更新:如果新旧子节点都是文本节点,直接更新文本内容。
  • 🎯 数组类型子节点的比对:如果新旧子节点都是数组,Vue 会通过 LIS 算法 来优化节点的重新排列,避免过多的 DOM 操作。

image.png

Vue3 diff 算法优化

  • 静态标记与动态节点的区分 Vue3引入了 静态标记(Static Marking) 机制,通过在模板编译阶段为静态节点添加标记,避免了对这些节点的重复比较。这使得Vue3能够更高效地处理静态内容,减少不必要的DOM操作。
  • 双端对比策略 Vue3的Diff算法采用了双端对比策略,即从新旧节点的头部和尾部同时开始比较,快速定位无序部分。这种策略显著减少了全量对比的复杂度,提升了性能。
  • 最长递增子序列(LIS)优化 在处理节点更新时,Vue3利用最长递增子序列(LIS)算法来优化对比流程。通过找到新旧节点之间的最长递增子序列,Vue3可以减少不必要的DOM操作,从而提高更新效率。
  • 事件缓存与静态提升 事件缓存:Vue3将事件缓存为静态节点,避免每次渲染时重新计算事件处理逻辑,从而减少性能开销。 静态提升:对于不参与更新的元素,Vue3将其提升为静态节点,仅在首次创建时进行处理,后续不再重复计算。
  • 类型检查与属性对比 Vue3在Diff算法中增加了类型检查和属性对比功能。如果节点类型不同,则直接替换;如果类型相同,则进一步对比节点的属性,生成更新操作。
  • 动态插槽的优化 Vue3对动态插槽进行了优化,通过动态节点的类型化处理,进一步提升了Diff算法的效率

四、vue 的组件传值方式有哪些

父子组件传值:props emit 祖孙组件传值:provide & inject 兄弟组件传值:vuex

五、谈谈对 vue-router 的理解

vue Router 的导航守卫用于在路由跳转过程中对导航行为进行拦截控制。这些守卫在路由进入、离开或更新时执行,可以用于多种场景,确保应用的导航逻辑符合预期。以下是常见的用途:

认证和授权

用于检查用户的登录状态或权限,防止未授权用户访问受限页面。

javascript

router.beforeEach((to, from, next) => {
  const isAuthenticated = !!localStorage.getItem('token')
  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login') // 未登录,跳转到登录页
  } else {
    next() // 已登录,正常导航
  }
})

数据预加载

在进入路由前预加载必要的数据,确保页面渲染时数据已准备好。

javascript

router.beforeEach(async (to, from, next) => {
  if (to.name === 'userInfo') {
    await store.dispatch('fetchUserData') // 预加载用户数据
  }
  next()
})

动态修改页面标题

根据路由信息动态更改浏览器标签页的标题,提升用户体验。

javascript

router.afterEach((to) => {
  document.title = to.meta.title || '自定义标题'
})

动画和加载效果

在路由切换时展示加载动画或过渡效果,提升用户体验。

javascript

router.beforeEach((to, from, next) => {
  store.commit('setLoading', true) // 开始加载动画
  next()
})

router.afterEach(() => {
  store.commit('setLoading', false) // 结束加载动画
})

日志记录和分析

在路由切换时记录用户行为,用于分析或调试。

javascript

router.afterEach((to, from) => {
  console.log(`用户从 ${from.fullPath} 跳转到 ${to.fullPath}`)
})

防止访问不存在的页面

通过守卫检查路由是否存在,避免导航到无效页面。

javascript

router.beforeEach((to, from, next) => {
  const routeExists = router.getRoutes().some((route) => route.name === to.name)
  if (!routeExists) {
    next('/404') // 跳转到 404 页面
  } else {
    next()
  }
})

六、vue nextTick 的实现原理

nextTick 的原理基于 Vue 的异步更新策略,通过将回调函数存储在队列中,并选择合适的异步方法在下次 DOM 更新循环结束后执行这些回调函数,确保开发者能够在 DOM 更新完成后进行相关操作。

如果你在修改数据后立即访问 DOM,可能会获取到更新前的 DOM 状态。nextTick 就是为了解决这个问题而设计的,它允许你在 DOM 更新完成后再执行回调函数,从而确保你能获取到最新的 DOM 状态。

 nextTick 实现的核心思路

nextTick 的核心思路是将回调函数存储在一个队列中,然后根据不同的环境选择合适的异步方法(如 PromiseMutationObserversetImmediate 或 setTimeout)来在异步任务中依次执行这些回调函数。

具体实现步骤

步骤一:定义回调队列

Vue 内部维护了一个回调队列,用于存储所有通过 nextTick 传入的回调函数。

步骤二:选择异步方法

根据不同的环境,Vue 会按以下顺序选择合适的异步方法来执行回调队列中的函数:

  • Promise:如果浏览器支持 Promise,则优先使用 Promise.then 来异步执行回调。
  • MutationObserver:如果浏览器不支持 Promise,则使用 MutationObserver 来实现异步执行。MutationObserver 可以监听 DOM 的变化,在 DOM 变化时触发回调。
  • setImmediate:如果浏览器既不支持 Promise 也不支持 MutationObserver,则使用 setImmediatesetImmediate 是一个在 IE 中支持的异步方法,用于在当前事件循环结束后立即执行回调。
  • setTimeout:如果以上方法都不支持,则使用 setTimeout 来异步执行回调,延迟时间设置为 0。
步骤三:执行回调函数

当选择好异步方法后,会在异步任务中依次执行回调队列中的所有回调函数。

七、谈谈常用的状态管理工具(vuex 和 pinia)

Pinia 和 Vuex 都是 Vue 的专属状态管理库,允许用户跨组件或页面共享状态。

区别

特性VuexPinia
版本支持Vue 2 和 Vue 3仅支持 Vue 3(基于 Composition API )
API 风格基于传统的对象式 API基于 Composition API,类似于 setup 语法
模块管理支持模块化(modules),但语法较复杂模块化简单,每个 store 就是一个独立模块
TypeScript 支持TypeScript 支持不完善,需手动定义类型开箱即用的 TypeScript 支持,类型推导更强大
性能更适合大型项目,但冗余代码较多更加轻量,性能更好,支持按需加载
状态持久化需要额外插件插件系统更加灵活,支持状态持久化插件

八、v-if 和 v-for 是否可以一起使用

由于v-for 的解析优先级高于 v-if,同时使用 v-if 和 v-for,Vue 首先会循环创建所有dom元素,然后根据条件来判断是否渲染每个元素,这种方式可能导致 Vue 进行大量的 DOM 操作,性能较差。其次,v-for 会为每个循环项创建一个新的作用域,而 v-if 的条件如果依赖于这个作用域内的数据,可能导致判断逻辑异常。

为避免上述问题,vue官方推荐我们将 v-if 放到 v-for 外层,或者将 v-if 放置到 v-for 内部的单个节点上。

js

<div v-if="show">
  <div v-for="item in list" :key="item.id">{{ item.name }}</div>
</div>

九、vue2 和 vue3 的区别(响应式、compositionApi、生命周期)

性能优化

  • 虚拟 DOM 重构:Vue3的虚拟DOM采用了更高效的 Diff算法,减少了渲染和更新的开销。
  • Tree-shaking 支持:Vue3的代码结构模块化,支持按需引入,减小了打包体积。

Composition API

  • Vue3引入了Composition API,使代码更模块化、复用性更强。
  • 使用 setup() 方法代替了部分选项式 API,通过函数的方式组织逻辑,代码更加清晰简洁。

响应式系统改进

  • Vue3使用 Proxy 实现响应式,解决了 Vue2使用Object.defineProperty实现响应式的一些局限性,如无法监听新增属性和数组索引变化。

新特性和改进

  • Teleport:可以将组件的DOM渲染到指定的DOM节点之外,例如模态框、通知等。
  • Fragment 支持:Vue3支持组件返回多个根节点,不再需要单一根节点。
  • Vue3原生支持 TypeScript,提供更完善的类型推导和开发体验。
  • Vue3支持为一个组件绑定多个 v-model,并且可以自定义 prop 和 event 名称。

生命周期

vue2生命周期
  • 创建阶段

1️⃣ beforeCreate:组件实例刚被创建,数据观测和事件/监听器设置之前。此时无法访问 data 、 computed 和 methods 等。

2️⃣ created:组件实例已创建,数据观测、事件/监听器设置完成,此时可以访问 data 、 computed 和 methods 等,通常用于数据初始化。

  • 挂载阶段

3️⃣ beforeMount:在挂载开始之前,模板已编译, el 和 template 已经确定,但尚未渲染。

4️⃣ mounted:组件实例挂载到 DOM 上之后,此时可以访问和操作 DOM。

  • 更新阶段

5️⃣ beforeUpdate:数据发生变化,DOM 尚未更新。可以在这里做一些数据处理,避免不必要的渲染。

6️⃣ updated:数据变化,DOM 更新后调用。此时组件的 DOM 已经更新,可以访问和操作新的 DOM。

  • 销毁阶段

7️⃣ beforeDestroy:组件实例销毁之前。可以在此阶段进行清理工作,例如移除事件监听器、定时器等。

8️⃣ destroyed:组件实例销毁之后。此时,所有的事件监听器和子组件已被销毁。

vue3生命周期
  • 创建阶段

1️⃣ onBeforeMount:等效于 Vue 2 中的 beforeMount ,在组件挂载之前调用。

2️⃣ onMounted:等效于 Vue 2 中的 mounted ,在组件挂载之后调用。

  • 更新阶段

3️⃣ onBeforeUpdate:等效于 Vue 2 中的 beforeUpdate ,在数据更新之前调用。

4️⃣ onUpdated:等效于 Vue 2 中的 updated ,在数据更新并渲染之后调用。

  • 销毁阶段

5️⃣ onBeforeUnmount:等效于 Vue 2 中的 beforeDestroy ,在组件卸载前调用。

6️⃣ onUnmounted:等效于 Vue 2 中的 destroyed ,在组件卸载后调用。

setup与生命周期

setup 作为 Vue3 的 Composition API 的一部分, 其内部函数的执行时机早于Mounted钩子。