一、谈谈对vue的理解
Vue 是数据驱动视图的 JavaScript 前端视图层框架,核心特点如下:
-
声明式渲染:通过模板语法声明数据与视图的绑定关系,无需手动操作 DOM 即可实现渲染。
-
响应式数据绑定:自动追踪数据变化,数据更新时,框架自动同步更新关联视图。
-
虚拟 DOM 优化:用 JavaScript 对象模拟真实 DOM,数据变化时通过 Diff 算法对比新旧虚拟 DOM,仅更新差异部分,实现批量高效更新。
-
组件化开发:以可复用的 Vue 实例构建组件,支持嵌套使用;组件间通过特定机制通信,数据变更时,基于响应式与虚拟 DOM 完成实例更新。
二、vue2/vue3 的响应式原理
1、vue2响应式原理
Vue 2 的响应式原理的核心是要理解 getter/setter
、Watcher
类(即副作用函数)、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 通过继承数组原型,对push
、pop
、shift
、unshift
、splice
、sort
、reverse
等方法进行重写。在重写的方法中,会先调用原始数组方法,然后手动触发更新,从而实现数组的响应式更新。 -
局限性
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()
无法劫持数组的所有方法,所以需要手动对push
、pop
、shift
、unshift
、splice
、sort
、reverse
等方法进行重写,以实现响应式更新。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 树的差异,并计算出最小的更新操作,最终更新相应的视图。
diff 算法的核心算法流程如下:
- 节点对比 如果新旧节点类型相同,则继续比较它们的属性。如果节点类型不同(如元素和文本节点不同),则直接替换整个节点。
- 属性更新: 如果节点类型相同,接下来检查节点的属性。对于不同的属性值进行更新,移除旧属性,添加新属性。
- 子节点比对: 对于有子节点的元素(如 div),Vue 会使用不同的策略来优化子节点更新:
- 🎯 文本节点的更新:如果新旧子节点都是文本节点,直接更新文本内容。
- 🎯 数组类型子节点的比对:如果新旧子节点都是数组,Vue 会通过
LIS 算法
来优化节点的重新排列,避免过多的 DOM 操作。
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
的核心思路是将回调函数存储在一个队列中,然后根据不同的环境选择合适的异步方法(如 Promise
、MutationObserver
、setImmediate
或 setTimeout
)来在异步任务中依次执行这些回调函数。
具体实现步骤
步骤一:定义回调队列
Vue 内部维护了一个回调队列,用于存储所有通过 nextTick
传入的回调函数。
步骤二:选择异步方法
根据不同的环境,Vue 会按以下顺序选择合适的异步方法来执行回调队列中的函数:
Promise
:如果浏览器支持Promise
,则优先使用Promise.then
来异步执行回调。MutationObserver
:如果浏览器不支持Promise
,则使用MutationObserver
来实现异步执行。MutationObserver
可以监听 DOM 的变化,在 DOM 变化时触发回调。setImmediate
:如果浏览器既不支持Promise
也不支持MutationObserver
,则使用setImmediate
。setImmediate
是一个在 IE 中支持的异步方法,用于在当前事件循环结束后立即执行回调。setTimeout
:如果以上方法都不支持,则使用setTimeout
来异步执行回调,延迟时间设置为 0。
步骤三:执行回调函数
当选择好异步方法后,会在异步任务中依次执行回调队列中的所有回调函数。
七、谈谈常用的状态管理工具(vuex 和 pinia)
Pinia
和 Vuex
都是 Vue 的专属状态管理库,允许用户跨组件或页面共享状态。
区别
特性 | Vuex | Pinia |
---|---|---|
版本支持 | 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钩子。