1. Vue 的核心特性是什么?
Vue 的核心特性主要包括:
- 响应式系统 :通过
Object.defineProperty(Vue 2)或Proxy(Vue 3)实现数据绑定,数据变化时自动更新视图。 - 模板语法 :基于 HTML 的声明式模板,支持插值、指令(如
v-if、v-for)等。 - 组件化 :将 UI 拆分为独立可复用的组件,支持单文件组件(
.vue文件)。 - 虚拟 DOM:通过虚拟 DOM 比对最小化真实 DOM 操作,提升性能。
2. Vue 2 和 Vue 3 的主要区别?
- 响应式原理 :
Vue 2 基于Object.defineProperty劫持对象属性的 getter/setter,存在缺陷:无法监听对象新增 / 删除属性、数组索引 / 长度变化,需通过 Vue.set/Vue.delete 解决;
Vue 3 基于 Proxy 代理整个对象,支持对象新增 / 删除属性、数组索引 / 长度变化,无需额外 API。Vue3 同时支持 reactive(对象 / 数组)和 ref(基础类型),响应式系统更灵活、性能更优。 - 性能优化:Vue 3 的虚拟 DOM 重写,渲染速度提升;支持 Tree-shaking(按需引入)。
- Composition API :Vue 3 提供
setup()函数替代data、methods等选项,逻辑组织更灵活。 - 生命周期 :
beforeDestroy和destroyed更名为beforeUnmount和unmounted。 - 多根节点支持:Vue 3 允许模板包含多个根节点(Fragment)。
3. 什么是 Vue 的生命周期钩子?常用钩子有哪些?
生命周期钩子是 Vue 组件在不同阶段执行的函数。常用钩子:
1、 创建期间的生命周期函数
beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性。created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中。mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示,DOM 已渲染。
2、运行期间的生命周期函数
beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点。updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。
3、销毁期间的生命周期函数
beforeUnmount(Vue 3)/beforeDestroy(Vue 2):实例销毁之前调用。在这一步,实例仍然完全可用。unmounted(Vue 3)/destroyed(Vue 2):实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

4. Vue 组件间通信有哪些方式?
- Props 和 Events :父组件通过
props向子组件传值,子组件通过$emit触发事件。 $parent/$children:直接访问父/子实例(不推荐,耦合度高)。- Provide / Inject :祖先组件通过
provide提供数据,后代组件通过inject注入。 - Event Bus :全局事件总线(Vue 2 常用,Vue 3 推荐用
mitt等库)。 - Vuex / Pinia:状态管理库,集中管理跨组件数据。
$refs:通过模板引用直接访问子组件方法或数据。
5. Vue 的响应式原理是什么?(Vue 3)
一、 响应式核心思想
Vue 响应式的本质是:通过拦截数据的读取与修改操作,建立 "数据依赖" 与 "更新逻辑" 的映射关系,当数据发生变化时,自动触发对应的更新逻辑(如视图渲染、回调执行等)。
简单拆解为 3 个核心步骤:
- 依赖收集:当数据被读取时(如模板渲染、计算属性依赖、侦听器监听),记录下 "谁在使用这个数据"(即依赖);
- 数据拦截:对数据的修改操作进行拦截,感知数据的变化;
- 派发更新:当数据发生变化时,通知所有之前收集的依赖,执行对应的更新逻辑(如重新渲染视图、执行 watch 回调)。
二、 Vue 2.x 响应式原理(Object.defineProperty 实现)
Vue 2.x 基于 Object.defineProperty API 实现响应式,仅支持对对象属性和数组方法的拦截,不支持原生 Map/Set 等数据结构。
- 核心 API:Object.defineProperty
Object.defineProperty 是 ES5 提供的 API,用于给对象定义或修改属性,并可以拦截该属性的 get(读取)和 set(修改)操作,这是 Vue 2.x 响应式的基础。
基础用法示例(模拟响应式拦截)
javascript
// 原始数据
const data = {
name: '张三',
age: 20
}
// 遍历对象,为每个属性添加响应式拦截
Object.keys(data).forEach(key => {
// 保存属性原始值
let value = data[key]
// 定义属性,拦截 get 和 set
Object.defineProperty(data, key, {
// 可枚举(遍历对象时能获取到该属性)
enumerable: true,
// 可配置(后续可修改属性配置)
configurable: true,
// get 方法:属性被读取时触发(依赖收集的时机)
get() {
console.log(`读取 ${key} 属性:${value}`)
// 依赖收集:此处会记录"谁读取了这个属性"
// Dep.target.addDep(this) // 简化示意,Vue 内部通过 Dep 类管理依赖
return value
},
// set 方法:属性被修改时触发(派发更新的时机)
set(newValue) {
if (newValue === value) return // 新值与旧值一致,不触发更新
console.log(`修改 ${key} 属性:${value} → ${newValue}`)
value = newValue // 更新原始值
// 派发更新:通知所有依赖该属性的对象执行更新逻辑
// dep.notify() // 简化示意,Vue 内部通过 Dep 类派发更新
}
})
})
// 测试:读取属性(触发 get)
console.log(data.name) // 输出:读取 name 属性:张三 → 张三
// 测试:修改属性(触发 set)
data.age = 21 // 输出:修改 age 属性:20 → 21
-
Vue 2.x 响应式核心模块
Vue 2.x 内部通过 3 个核心类配合 Object.defineProperty 实现完整响应式:
类名 作用
Observer 递归遍历对象的所有属性,为每个属性添加 get/set 拦截,将普通对象转为响应式对象;对数组则重写其 7 个变异方法(push/pop/shift/unshift/splice/sort/reverse)。
Dep(Dependency) 依赖管理器,每个响应式属性对应一个 Dep 实例,用于收集(addDep)和存储该属性的所有依赖(Watcher 实例),并在数据变化时派发更新(notify)。
Watcher 依赖的载体(也叫观察者),分为渲染 Watcher(对应模板视图)、计算属性 Watcher、侦听器 Watcher 三种。当数据变化时,Dep 会通知 Watcher 执行 update 方法,触发对应的更新逻辑(如重新渲染视图、执行 watch 回调)。
-
Vue 2.x 响应式执行流程(完整链路)
初始化阶段:new Vue() 后,Vue 会调用 Observer 对 data 中的对象进行递归遍历,为每个属性添加 get/set 拦截,同时为每个属性创建 Dep 实例;
模板渲染阶段:解析模板时,会创建渲染 Watcher,并将 Dep.target 指向该 Watcher;当读取 data 中的属性时(如 {{ name }}),触发 get 方法,Dep 会将当前 Watcher 添加到依赖列表中(依赖收集);
数据修改阶段:当通过 this.name = '李四' 修改属性时,触发 set 方法,对应的 Dep 实例会调用 notify 方法,遍历所有收集的 Watcher,执行 update 方法;
更新视图阶段:Watcher 的 update 方法会触发模板重新解析和渲染,最终实现视图与数据的同步。
-
Vue 2.x 响应式的局限性
无法监听对象新增 / 删除属性:Object.defineProperty 只能拦截已存在的属性,新增属性(this.data.newKey = 'xxx')或删除属性(delete this.data.key)不会触发视图更新,需要通过 Vue.set/this.set、Vue.delete/this.set、Vue.delete/this.set、Vue.delete/this.delete 手动触发;
无法监听数组索引和长度变化:由于性能原因,Vue 2.x 没有对数组索引和长度的修改进行拦截(如 arr[0] = 10、arr.length = 0),只能通过重写的 7 个变异方法触发更新;
对复杂数据结构支持有限:不支持原生 Map、Set、WeakMap 等数据结构的响应式监听;
递归拦截性能损耗:Observer 会递归遍历对象的所有属性,当对象层级过深、属性过多时,会影响初始化性能。
三、 Vue 3.x 响应式原理(Proxy 实现)
Vue 3.x 放弃了 Object.defineProperty,转而使用 ES6 提供的 Proxy API 实现响应式,同时配合 Reflect API 进行属性操作,解决了 Vue 2.x 的所有局限性,且性能更优。
- 核心 API:Proxy + Reflect
(1) Proxy 特性
Proxy 用于创建一个对象的 "代理",可以拦截该对象的所有属性操作(包括读取、修改、新增、删除、遍历等),相比 Object.defineProperty 具有更强的拦截能力,且直接作用于整个对象,而非单个属性。
(2) Reflect 特性
Reflect 同样是 ES6 提供的 API,用于执行对象的原生操作,它的方法与 Proxy 的拦截方法一一对应。Vue 3.x 中使用 Reflect 的目的:
确保属性操作的行为与原生一致;
可以获取属性操作的返回值(如 Reflect.set 会返回布尔值,表示设置是否成功);
解决 this 指向问题,避免代理对象操作时的上下文丢失。
基础用法示例(模拟 Vue 3.x 响应式)
javascript
// 原始数据
const data = {
name: '张三',
age: 20,
hobbies: ['篮球', '读书']
}
// 创建 Proxy 代理,拦截整个对象
const reactiveData = new Proxy(data, {
// get 方法:拦截属性读取(包括对象属性、数组索引、原型属性等)
get(target, key, receiver) {
console.log(`读取 ${key} 属性:${target[key]}`)
// 依赖收集:此处记录依赖关系
// track(target, key) // Vue 3 内部通过 track 函数收集依赖
// 使用 Reflect 读取属性,确保行为原生
return Reflect.get(target, key, receiver)
},
// set 方法:拦截属性修改、新增(包括对象属性、数组索引等)
set(target, key, newValue, receiver) {
console.log(`修改/新增 ${key} 属性:${target[key]} → ${newValue}`)
// 派发更新:通知依赖执行更新逻辑
// trigger(target, key) // Vue 3 内部通过 trigger 函数派发更新
// 使用 Reflect 设置属性,返回操作结果
return Reflect.set(target, key, newValue, receiver)
},
// deleteProperty 方法:拦截属性删除
deleteProperty(target, key) {
console.log(`删除 ${key} 属性`)
// 派发更新
// trigger(target, key)
// 使用 Reflect 删除属性,返回操作结果
return Reflect.deleteProperty(target, key)
}
})
// 测试 1:读取属性(触发 get)
console.log(reactiveData.name) // 输出:读取 name 属性:张三 → 张三
// 测试 2:修改属性(触发 set)
reactiveData.age = 21 // 输出:修改/新增 age 属性:20 → 21
// 测试 3:新增属性(触发 set,Vue 2.x 不支持此特性)
reactiveData.gender = '男' // 输出:修改/新增 gender 属性:undefined → 男
// 测试 4:删除属性(触发 deleteProperty,Vue 2.x 不支持此特性)
delete reactiveData.age // 输出:删除 age 属性
// 测试 5:修改数组索引(触发 set,Vue 2.x 不支持此特性)
reactiveData.hobbies[0] = '足球' // 输出:修改/新增 0 属性:篮球 → 足球
- Vue 3.x 响应式核心函数
Vue 3.x 采用函数式设计,取消了 Vue 2.x 的 Observer、Dep 类,通过以下核心函数实现响应式:
函数 作用
reactive 接收一个普通对象 / 数组,返回其 Proxy 代理对象,实现对象类型数据的响应式(深层响应式);支持新增、删除属性,支持数组索引 / 长度修改。
ref 接收基本数据类型(string/number/boolean 等)或对象,返回一个带有 .value 属性的 Ref 实例,解决基本数据类型无法被 Proxy 拦截的问题;访问 / 修改其值时需通过 .value(模板中可省略)。
track 依赖收集函数,在 Proxy 的 get 拦截中调用,记录 "目标对象 - 属性 - 依赖" 的映射关系,依赖存储在 WeakMap 结构中(更节省内存)。
trigger 派发更新函数,在 Proxy 的 set/deleteProperty 拦截中调用,根据 "目标对象 - 属性" 找到所有依赖,执行对应的更新逻辑。
effect 副作用函数,包裹需要响应式执行的逻辑(如模板渲染、计算属性逻辑、watch 回调);当依赖的数据变化时,副作用函数会自动重新执行。 - Vue 3.x 响应式的优势
更强的拦截能力: 支持对象新增 / 删除属性、数组索引 / 长度修改,无需手动调用 set/set/set/delete;
支持更多数据类型: 原生支持 Map、Set、WeakMap、WeakSet 等复杂数据结构的响应式监听;
性能更优:
- 非递归初始化:Proxy 直接作用于整个对象,初始化时无需递归遍历所有属性,仅在属性被读取时才进行深层代理(懒代理),提升初始化性能;
- 内存更高效:使用 WeakMap 存储依赖关系,依赖对象被销毁时会自动释放内存,避免内存泄漏;
支持基本数据类型: 通过 ref 函数实现基本数据类型的响应式,弥补了 Proxy 无法直接拦截基本数据类型的缺陷;
更好的兼容性: 配合 Reflect API,解决了 Object.defineProperty 的诸多兼容性问题,且更符合 ES 规范。
四、 响应式原理关键总结
-
核心本质:拦截数据操作(读取 / 修改 / 新增 / 删除),建立 "数据 - 依赖"
映射,数据变化自动触发依赖更新,实现视图与数据同步;
-
版本差异:Vue 2.x 基于 Object.defineProperty(单个属性拦截,有局限性),Vue 3.x 基于 Proxy
- Reflect(整个对象拦截,功能更强、性能更优);
-
核心步骤:依赖收集(读取数据时)→ 数据拦截(监听数据变化)→ 派发更新(通知依赖执行更新逻辑);
-
补充点:Vue 3.x 中 reactive 用于对象类型,ref 用于基本类型,二者共同覆盖所有数据类型的响应式需求;
-
核心优势:无需手动操作 DOM,简化开发流程,提升开发效率,同时保证视图与数据的一致性。
6. 什么是 Computed 和 Watch?区别是什么?
Computed(计算属性)
1. 核心概念
Computed 即计算属性,它是基于依赖的响应式数据进行缓存计算的属性。简单来说,它是一个 "派生值"------ 通过已有数据(Vue 实例的 data/props/ 其他计算属性)经过逻辑处理后得到的新值,本质是一个带有缓存的 getter 函数(可配置 setter 成为读写属性)。
2. 核心特性
- (1) 缓存机制(最核心特性)
计算属性会缓存计算结果,只有当它的依赖数据发生变化时,才会重新执行计算逻辑,返回新的结果;若依赖数据未改变,多次访问计算属性会直接返回缓存的旧结果,不会重复计算。
示例理解:若 fullName 依赖 firstName 和 lastName,只要 firstName 和 lastName 不变,无论多少次访问 fullName,都不会重新拼接字符串,直接返回缓存值。 - (2) 声明式编程
Computed 采用 "声明式" 写法,我们只需要声明 "计算结果依赖什么数据""如何得到结果",无需关心数据变化的时机,Vue 会自动追踪依赖并完成更新。 - (3) 默认只读(可配置为读写)
计算属性默认只有 getter 方法,即仅支持获取值,不支持直接修改;若需要修改计算属性,可手动配置 setter 方法。 - (4) 必须返回一个值
计算属性的核心是 "计算出一个结果",因此 getter 函数必须有返回值,这个返回值就是计算属性的最终值。
Watch(侦听器)
1. 核心概念
Watch 即侦听器(也叫监听器),它的核心作用是主动监听一个或多个响应式数据的变化,当数据发生改变时,执行指定的回调函数(副作用函数),用于处理复杂的业务逻辑(如异步操作、多步骤数据处理等)。
2. 核心特性
- (1) 无缓存机制
Watch 没有缓存,只要被监听的数据发生变化,就会立即执行回调函数,无论回调函数的逻辑是否产生相同结果,都会重复执行。 - (2) 命令式编程
Watch 采用 "命令式" 写法,我们需要明确指定 "监听哪个数据""数据变化后执行什么逻辑",主动处理数据变化的后续操作。 - (3) 支持异步操作
这是 Watch 与 Computed 的关键区别之一,Watch 的回调函数中可以自由执行异步操作(如接口请求、定时器等),而 Computed 的 getter 函数不支持异步(异步操作会导致无法及时返回计算结果,破坏缓存机制)。 - (4) 可监听单个 / 多个数据
Watch 既可以监听单个响应式数据,也可以监听多个响应式数据,还能深度监听对象 / 数组的内部属性变化。 - (5) 无强制返回值要求
Watch 的回调函数是用于执行 "副作用"(如修改其他数据、发起请求、操作 DOM 等),无需返回任何值。
7. Vuex 的核心概念有哪些?
答案:
- State:存储应用状态的单一数据源。
- Mutations :同步修改 State 的方法(通过
commit触发)。 - Actions :处理异步操作,通过
dispatch触发,可调用 Mutations。 - Getters:计算派生状态(类似 Computed)。
- Modules:将 Store 分割为模块,便于大型项目管理。
8. Vue Router 的路由守卫有哪些?
路由守卫用于控制导航流程:
- 全局守卫 :
beforeEach、beforeResolve、afterEach。 - 路由独享守卫 :在路由配置中定义的
beforeEnter。 - 组件内守卫 :
beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。
Vue Router 支持两种路由模式,核心区别在于 URL 格式和底层实现原理,分别适配不同场景。
- history 模式(推荐,优雅 URL)
(1) 实现原理
基于 HTML5 History API(pushState、replaceState、popstate 事件),通过操作浏览器的历史记录栈,修改 URL 路径而不发起 HTTP 请求。
pushState: 添加一条新的历史记录,修改 URL 路径(对应 router.push);
replaceState: 替换当前历史记录,修改 URL 路径(对应 router.replace);
popstate: 监听浏览器前进 / 后退按钮触发的历史记录变化,Vue Router 内部通过该事件实现视图切换。
(2) URL 格式
无 # 号,与传统网站 URL 一致,更优雅美观:
plaintext
https://xxx.com/article?category=vue
(3) 注意事项
依赖后端配置:因为 SPA 只有一个 HTML 文件,当用户直接访问非根路径(如 https://xxx.com/user/1001)时,后端会默认查找该路径对应的文件,导致 404 错误;
后端解决方案:配置所有路由请求都重定向到 SPA 的入口 HTML 文件(如 index.html),Nginx/Apache/NestJS 等均有对应配置方案。
- hash 模式(兼容模式,无需后端配置)
(1) 实现原理
基于 URL 的 hash 部分(# 后面的内容,如 https://xxx.com/#/home),hash 变化不会触发浏览器的页面刷新,也不会发起 HTTP 请求,Vue Router 通过监听 hashchange 事件感知 hash 变化,进而切换视图。
(2) URL 格式
带有 # 号,兼容性更好(支持 IE 10 及以下),无需后端配置:
plaintext
https://xxx.com/#/article?category=vue
(3) 优缺点
优点:兼容性强、无需后端配置、使用简单;
缺点:URL 带有 # 号,不够优雅;部分第三方服务(如微信支付)可能会忽略 hash 部分,导致参数传递异常。
9. 如何优化 Vue 应用性能?
- 虚拟 DOM 优化 :减少不必要的渲染(如用
v-show替代v-if频繁切换的组件)。 - 使用 Computed 缓存:避免重复计算。
- 异步组件 :通过
defineAsyncComponent或() => import()实现按需加载。 - 列表性能 :为
v-for添加key,避免使用v-if和v-for同时作用。 - 事件销毁 :在
beforeUnmount中移除全局事件监听(如window.addEventListener)。
10. Vue 3 的 Composition API 解决了什么问题?
- 逻辑复用 :通过自定义 Hook(如
useFetch)替代 Mixins,避免命名冲突。 - 代码组织 :将同一功能的代码(如数据、方法)聚合在
setup()中,提高可读性。 - TypeScript 支持:更好的类型推断,减少类型声明冗余。
附:Vue 3 组合式 API 示例
javascript
import { ref, computed, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const double = computed(() => count.value * 2);
const increment = () => count.value++;
onMounted(() => {
console.log('组件已挂载');
});
return { count, double, increment };
}
};
以上问题覆盖了 Vue 的核心知识点,建议结合实践加深理解!