
📋 目录
Q1. Vue 的核心是什么?对比 Vue 2 和 Vue 3 的响应式原理有什么区别
Q2. 什么是 MVVM 模式?Vue 是如何体现 MVVM 的?
Q3. Vue 实例的选项有哪些?(例如 data、computed、methods、watch、生命周期等)
Q4. v-if 和 v-show 有什么区别?分别适用于什么场景?
Q5. v-for 和 v-if 为什么不推荐一起使用?有什么替代方案?
Q6. Vue 中如何实现父子组件通信?(props + $emit / v-model)
Q7. 什么是插槽(Slot)?slot 和 slot-scope 在 Vue 2 和 Vue 3 中有什么区别?
Q8. computed 和 watch 有什么区别?各自的使用场景是什么?
Q9. 什么是 nextTick?它的实现原理是什么?
Q10. Vue 3 的 Composition API 和 Options API 有什么区别?
Q1. Vue 的核心是什么?对比 Vue 2 和 Vue 3 的响应式原理有什么区别
考点
Vue 渐进式框架理念、响应式数据绑定的核心机制、Vue 2 与 Vue 3 响应式实现原理的底层差异。
答案
Vue 的核心是响应式数据绑定和组件化系统
。
Vue 是一个渐进式的 JavaScript 框架,所谓"渐进式"是指它可以按需使用:可以用 CDN 引入做局部页面增强,也可以构建完整的单页应用(结合 Vue Router、Vuex/Pinia、Vite 等)。其核心思想有三点:
响应式数据绑定:数据(Model)和视图(View)自动同步,当数据变化时视图自动更新,开发者无需手动操作 DOM。
组件化系统:将 UI 拆分为独立、可复用的组件,每个组件包含自己的模板、逻辑、样式。
声明式渲染:用模板语法描述"想要什么样子",由 Vue 负责"怎么做到"。
Vue 2 的响应式原理

Vue 2 通过Object.defineProperty拦截对象属性的读取和设置,在读取时收集依赖(哪些地方用到了这个数据),在设置时通知依赖更新。它的核心思路是:把 data 中定义的每个属性都转换成带有 getter/setter 的"响应式属性",当属性被访问时把当前的 Watcher 收集到依赖列表中,属性被修改时再依次通知这些 Watcher 重新执行。
但 Vue 2 这种实现方式存在三个明显缺陷:
无法监听对象新增/删除属性:因为
Object.defineProperty只能拦截已存在的属性,属性在初始化时就被处理完毕。后续给对象添加新字段,Vue 2 不会感知,必须通过Vue.set/this.$set手动注入响应式。无法监听通过数组下标直接赋值和修改数组长度:Vue 2 出于性能考虑对数组做了特殊处理,没有为数组的每个下标建立响应式。要触发更新必须使用数组的"变异方法"(push、pop、shift、unshift、splice、sort、reverse),或者通过
this.$set/this.$delete显式处理。必须递归遍历嵌套对象:初始化时就要深度遍历整个 data,对所有嵌套属性调用
Object.defineProperty,首次渲染开销大。
Vue 3 的响应式原理
Vue 3 使用 ES6 的Proxy重写了响应式系统。Proxy可以理解成"对象的代理器",可以在代理对象的读取、设置、新增、删除、in 操作等 13 种行为上做拦截,从根本上解决了Object.defineProperty的限制。
Vue 3 的响应式建立在三个核心函数之上:
reactive:把一个普通对象包装成 Proxy 代理对象,访问属性时自动追踪依赖,修改属性时自动触发更新。同时采用"懒递归"策略——只有真正访问到嵌套对象时才会把它也包装成 reactive,避免初始化时的全量递归。
effect / watch:用来声明"副作用",比如更新 DOM、发起网络请求等。它会立即执行一次传入的函数,并在函数内部访问到的所有响应式属性上注册自己,当这些属性变化时 effect 会自动重新执行。
track / trigger:依赖追踪与触发更新的底层机制。track 在 getter 中收集"哪个 effect 依赖了哪个属性",trigger 在 setter 中根据属性找到所有依赖的 effect 并依次执行。
Vue 2 vs Vue 3 响应式对比
新增属性响应式:Vue 2 需要
$set,Vue 3 原生支持。删除属性响应式:Vue 2 需要
$delete,Vue 3 原生支持。数组索引赋值:Vue 2 需要
$set,Vue 3 原生支持。嵌套对象递归方式:Vue 2 初始化时全量递归,Vue 3 是按需懒递归。
性能:Vue 2 首次渲染开销大,Vue 3 性能更优。
支持的数据类型:Vue 2 只支持对象/数组,Vue 3 的 Proxy 还支持 Map、Set、WeakMap、WeakSet 等。
实际项目中的区别
在 Vue 2 中如果要给表单动态加字段,必须调用this.$set(this.form, 'newField', value),否则输入框不会响应。在 Vue 3 中直接form.newField = value即可,Proxy 会自动追踪这个新属性。这也是为什么 Vue 3 在处理动态表单、动态列配置等场景时比 Vue 2 优雅得多。
知识点详解
渐进式框架
"渐进式"的核心含义是不强制全栈。开发者可以从最简单的场景开始使用 Vue:
// 第一步:只做模板字符串替换new Vue({el: '#app',data: { message: 'Hello' }});
随着项目复杂度提升,可以逐步引入组件系统、Vue Router、Vuex/Pinia、构建工具等。这种设计降低了学习曲线,也避免了"为了用框架而重构项目"的问题。
Object.defineProperty 原理
Object.defineProperty是 ES5 的 API,可以拦截对象属性的读取和赋值:
// 简化版 Vue 2 响应式实现function defineReactive(obj, key, val) {const dep = new Dep(); // 依赖收集器Object.defineProperty(obj, key, {get() {// 读取时收集依赖:把当前 Watcher 加入 depdep.depend();return val;},set(newVal) {if (newVal === val) return;val = newVal;// 设置时通知更新:dep 中所有 Watcher 重新执行dep.notify();}});}function observe(obj) {// 遍历所有属性,逐个 defineReactiveObject.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));}const data = { count: 0 };observe(data);data.count; // 触发 get,收集依赖data.count = 1; // 触发 set,通知更新
关键限制:必须在observe阶段已知属性才能拦截。所以 Vue 2 在初始化时就要遍历整个 data 对象,对每个属性调用Object.defineProperty。后续动态添加的属性不会被拦截——这就是Vue.set存在的意义。
const vm = new Vue({ data: { user: { name: '张三' } } });// ❌ 不会触发更新:age 不在初始化时定义的属性中vm.user.age = 18;// ✅ 手动添加响应式Vue.set(vm.user, 'age', 18);// 或 this.$set(vm.user, 'age', 18)
Proxy 原理
ES6 的Proxy可以"代理"整个对象,拦截更多操作:
// 简化版 Vue 3 reactive 实现function reactive(target) {return new Proxy(target, {// 拦截属性读取get(target, key, receiver) {const result = Reflect.get(target, key, receiver);// 追踪依赖track(target, key);// 懒递归:访问到嵌套对象时才包装if (isObject(result)) return reactive(result);return result;},// 拦截属性赋值set(target, key, value, receiver) {const result = Reflect.set(target, key, value, receiver);// 触发更新trigger(target, key);return result;},// 拦截属性删除deleteProperty(target, key) {const result = Reflect.deleteProperty(target, key);trigger(target, key);return result;},// 拦截 in 操作has(target, key) {const result = Reflect.has(target, key);track(target, key);return result;}});}
相比Object.defineProperty,Proxy 有三大优势:
支持新增/删除属性:不需要
$set/$delete,直接obj.newKey = value即可。支持数组索引赋值:直接
arr[0] = newValue会触发更新。懒递归:只有真正访问嵌套对象时才包装为 reactive,初始化开销小。
依赖收集与派发更新
响应式系统的核心是三个角色:数据源、副作用、调度器。
// 简化的依赖收集与触发const targetMap = new WeakMap(); // target -> key -> Set<effect>function track(target, key) {let depsMap = targetMap.get(target);if (!depsMap) targetMap.set(target, (depsMap = new Map()));let dep = depsMap.get(key);if (!dep) depsMap.set(key, (dep = new Set()));dep.add(activeEffect); // 把当前活跃的 effect 加入依赖}function trigger(target, key) {const depsMap = targetMap.get(target);if (!depsMap) return;const dep = depsMap.get(key);if (dep) dep.forEach(effect => effect.run()); // 依次执行 effect}let activeEffect = null;function effect(fn) {activeEffect = { run: fn };fn(); // 立即执行,期间访问到的响应式数据会自动建立依赖activeEffect = null;}
关键点:
track在 getter 中调用,收集"哪个 effect 依赖了哪个 key"。trigger在 setter 中调用,触发"所有依赖这个 key 的 effect"重新执行。effect 嵌套时用栈管理,清理时恢复到外层 effect。
批量更新与异步调度
Vue 不会在数据变化时立即执行 effect,而是合并到下一个 tick 批量执行:
// 简化的调度器let queue = new Set();let isFlushing = false;function queueJob(job) {queue.add(job);if (!isFlushing) {isFlushing = true;// 用微任务在同步代码执行完后批量执行Promise.resolve().then(flushJobs);}}function flushJobs() {queue.forEach(job => job());queue.clear();isFlushing = false;}
为什么用微任务?微任务在同步代码结束后立即执行(在渲染前),能保证 nextTick 回调在 DOM 更新后被调用。
ref 与 reactive 的区别
import { ref, reactive } from 'vue';// ref 包装基本类型const count = ref(0);console.log(count.value); // 0count.value++; // 1// reactive 包装对象const state = reactive({ name: '张三', age: 25 });console.log(state.name); // '张三',不需要 .valuestate.age = 30;// 模板中 ref 会被自动解包// {{ count }} 等价于 {{ count.value }}
选择建议:
基本类型 →
ref。对象/数组 →
reactive(属性访问更简洁)。需要整体替换的对象 →
ref(reactive 整体替换会丢失引用)。大型项目统一用
ref,避免混用带来的认知负担。
Q2. 什么是 MVVM 模式?Vue 是如何体现 MVVM 的?
考点
MVVM 架构模式的核心概念、Vue 与 MVVM 的对应关系、MVVM 相比 MVC/MVP 的优势。
答案
MVVM(Model-View-ViewModel)是一种软件架构模式,核心思想是让 View 和 Model 通过 ViewModel 实现自动双向同步,减少手动 DOM 操作。
MVVM 三层职责
View(视图层):用户能看到的界面,负责把数据渲染出来并接收用户输入。在 Vue 中对应模板(template)。
ViewModel(视图模型层):是 View 和 Model 之间的桥梁,一方面把 Model 转换成 View 需要的展示数据(数据格式化、过滤、计算),另一方面处理 View 层的交互逻辑(事件处理)。在 Vue 中对应 Vue 实例本身(data + methods + computed + watch 共同构成 ViewModel)。
Model(数据层):业务数据和业务逻辑,通常来自后端接口、JSON 数据或本地状态。在 Vue 中对应 data 中存储的原始数据(最终可能来自后端 API)。
Vue 中 MVVM 的体现
Vue 是 MVVM 模式的典型实现。开发者只需要关注"数据是什么样子"和"视图怎么展示数据"两部分,View 和 Model 之间的同步工作完全由 Vue 框架自动完成:
当 Model(data 中的数据)发生变化时,View(模板)会自动重新渲染。
当用户在 View 上输入(如 v-model 绑定输入框)时,Model 也会自动更新。
整个过程中开发者不需要写
document.getElementById或innerHTML =这种命令式代码。
MVVM 与 MVC/MVP 的区别
MVC(Model-View-Controller):Controller 负责处理所有业务逻辑和流程控制,但随着业务膨胀,Controller 容易变得臃肿,且 View 通常直接和 Controller 通信。
MVP(Model-View-Presenter):Presenter 持有 View 的引用,代替 View 处理业务逻辑,View 和 Presenter 是耦合的(Presenter 需要知道 View 的存在)。
MVVM:ViewModel 不持有 View 的引用,通过数据绑定实现自动同步,View 和 ViewModel 是解耦的,开发体验最好。
实际项目中的 MVVM 应用
以电商订单列表页为例:
Model:从后端
/api/orders接口返回的订单数组,每条订单有订单号、状态、金额等字段。View:订单搜索框、状态筛选下拉框、订单卡片列表(用 v-for 渲染)、空状态提示。
ViewModel:
data 中保存原始订单列表、loading 状态、搜索关键词、筛选条件。
computed 中根据搜索关键词和筛选条件过滤订单,得到"视图需要的格式"。
watch 中监听订单变化,触发统计更新等副作用。
methods 中处理"重置筛选""跳转到详情页"等交互。
整个过程中,View 完全不知道 Model 怎么来的,Model 也不知道 View 长什么样,ViewModel 是它们的协调者。
知识点详解
双向绑定的本质
MVVM 强调的"双向"实际上是由两个单向构成的:Model → View(数据驱动视图)和 View → Model(视图驱动数据)。Vue 中的v-model把这两个单向绑定封装成了语法糖:
<!-- v-model 的本质:value 绑定 + input 事件监听 --><input:value="message" @input="message = $event.target.value" /><!-- 完全等价于 --><inputv-model="message" />
React 中没有 v-model 语法糖,必须手动实现:
// React 中的双向绑定(手动实现)function App() {const [message, setMessage] = useState('');return (<inputvalue={message}onChange={e => setMessage(e.target.value)}/>);}
ViewModel 为什么不持有 View 引用
这是 MVVM 与 MVP 最大的区别。
// MVP 风格:Presenter 直接调用 View 的方法(强耦合)classPresenter {constructor(view) {this.view = view; // 持有 View 引用}loadData() {fetch('/api/data').then(data => {this.view.showLoading(); // 调用 Viewthis.view.renderList(data); // 调用 View});}}// MVVM 风格:ViewModel 只维护数据,View 通过数据驱动渲染new Vue({data: { loading: false, list: [] },created() {fetch('/api/data').then(data => {this.loading = false; // 改数据,View 自动更新this.list = data; // 改数据,View 自动更新});}});
优势:ViewModel 不需要知道 View 的存在,单元测试更容易(直接测试数据逻辑),跨端复用更容易(同一份 ViewModel 可对接 Web、小程序、App 的不同 View)。
数据驱动视图的演进
前端开发的范式经历了三个阶段:
| 阶段 | 代表 | 思维模型 | 典型 API |
|---|---|---|---|
| 命令式 DOM 操作 | jQuery | "我要怎么改 DOM" | $el.html('...') |
| 模板引擎 | Mustache/Handlebars | "模板 + 数据 = HTML" | Mustache.render(tpl, data) |
| 数据驱动视图 | Vue/React | "我维护好数据,框架帮我更新视图" | this.list = newList |
MVVM 是数据驱动视图的典型实现,开发者从"如何操作 DOM"转变为"如何维护数据状态",思维模型发生了根本变化。
MVVM 与响应式的关系
// 同样的需求在两种模式下的实现// 1. 命令式:手动监听变化 + 手动更新 DOMconst data = { count: 0 };const el = document.getElementById('count');Object.defineProperty(data, 'count', {set(newVal) {el.textContent = newVal; // 手动更新 DOM}});// 2. MVVM:声明式,框架自动同步new Vue({data: { count: 0 },template: '<div>{{ count }}</div>' // 数据变化自动更新});
MVVM 是一种架构思想,响应式是实现手段。Vue 通过响应式系统(Proxy/Object.defineProperty)实现了 MVVM 中的"自动同步"。但 MVVM 并不限于 Vue——Knockout.js、AngularJS、San.js 等都遵循 MVVM 思想,只是实现方式不同。
编编留言:
内容篇幅有些长,将题目做拆解,下一篇再继续学习,加油!各位童鞋

