
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 有什么区别?
JavaScript 事件循环与 Vue 响应式更新的关系、nextTick 的使用场景、以及微任务/宏任务的底层原理。
nextTick 是 Vue 提供的一个 API,用于在 DOM 更新完成之后执行回调函数。
为什么需要 nextTick
Vue 的响应式更新是异步的。当响应式数据变化时,Vue 不会立即更新 DOM,而是把更新操作放入一个异步更新队列,在下一个 tick 批量执行。这是为了避免同一个 tick 内多次修改数据导致 DOM 重复渲染,是一种性能优化。
但这给开发者带来一个问题:数据变化后立即访问 DOM,拿到的还是旧值。nextTick 就是用来解决这个问题的——它允许你注册一个回调,回调会在 DOM 更新完成后执行。
典型使用场景
v-if 切换后操作新渲染的 DOM:例如点击按钮显示输入框后立即聚焦。
异步数据加载后操作 DOM:例如接口返回后滚动到列表顶部。
更新数据后读取布局信息:例如表单提交后计算新元素的高度。
依赖子组件渲染完成的操作:例如父组件修改 props 后读取子组件的 DOM 状态。
nextTick 的实现原理
nextTick 的核心思想是利用 JavaScript 的微任务机制。Vue 在不同环境中按优先级选择不同的实现方案:
Promise.then(微任务):最优先,效率最高。
MutationObserver(微任务):在不支持 Promise 的环境降级使用。
setImmediate(宏任务):仅 Node.js 环境。
setTimeout(fn, 0)(宏任务):最终降级方案,兼容性最好但开销最大。
为什么优先用微任务
微任务在当前同步代码执行完毕后立即执行(在渲染前),宏任务要等下一轮事件循环(在渲染后)。Vue 把 nextTick 回调放在微任务中,能保证DOM 更新完成后才执行。如果用宏任务,回调可能在 DOM 更新前执行,失去 nextTick 的意义。
Vue 异步更新队列机制
当响应式数据变化时,Vue 不会立即更新 DOM,而是:
通知所有依赖这个数据的 Watcher / 渲染 watcher。
把这些 Watcher 放入一个队列中。
在下一个 tick 批量执行队列中的 Watcher(同一个 tick 内多次修改同一个数据只更新一次)。
队列清空后,触发 nextTick 回调。
这种批量更新机制保证了:
同一个 tick 内多次修改数据,DOM 只更新一次(性能优化)。
开发者可以在 nextTick 回调中拿到最新 DOM(同步问题解决)。
nextTick 的使用方式
回调函数形式:this.$nextTick(() => { /* DOM 更新后执行 */ })。
Promise 形式(推荐):await this.$nextTick(),async/await 风格更清晰。
Vue 3 中:import { nextTick } from 'vue' 后调用,行为相同。
实际项目中的 nextTick 应用
v-if 后聚焦输入框:先 this.showInput = true,再 await this.$nextTick(),然后 this.$refs.input.focus()。
列表数据更新后滚动:先 this.items = await fetchItems(),再 await this.$nextTick(),然后 this.$refs.list.scrollTop = 0。
表单提交后调整布局:先更新表单数据,再 nextTick 读取新元素高度调整布局。
// 执行顺序示例
console.log('1'); // 同步任务
setTimeout(() =>console.log('2'), 0); // 宏任务
Promise.resolve().then(() =>console.log('3')); // 微任务
console.log('4'); // 同步任务
// 输出顺序:1, 4, 3, 2
// 同步 -> 微任务 -> 渲染 -> 宏任务
任务分类:
同步任务:在主线程上顺序执行。
微任务:Promise.then、queueMicrotask、MutationObserver 回调。在同步任务结束后、渲染前执行。
宏任务:setTimeout、setInterval、setImmediate、I/O、UI 渲染等。在下一轮事件循环执行。
// Vue 3 源码简化
let pending = false;
const queue = [];
function queueJob(job) {
queue.push(job);
if (!pending) {
pending = true;
// 优先用微任务
Promise.resolve().then(flushJobs);
}
}
function flushJobs() {
// 批量执行所有更新任务
for (const job of queue) {
job();
}
queue.length = 0;
pending = false;
}
export function nextTick(callback) {
return new Promise(resolve=> {
queueJob(() => {
if (callback) callback();
resolve();
});
});
}
// ❌ 不批量:每次修改都更新 DOM,性能差
this.count = 1; // DOM 更新
this.count = 2; // DOM 更新
this.count = 3; // DOM 更新
// ✅ 批量:合并到下一个 tick,只更新一次
this.count = 1; // 加入队列
this.count = 2; // 合并到队列
this.count = 3; // 合并到队列
// 下一个 tick 执行一次 DOM 更新
如果不批量更新,每次数据变化都立即操作 DOM,会造成严重的性能问题(DOM 操作是昂贵的)。
export default {
data() {
return {
showInput: false,
message: 'Hello'
};
},
methods: {
handleClick() {
this.showInput = true; // 数据变化,但 DOM 还没更新
console.log(this.$refs.input); // ❌ undefined:input 还没渲染
this.$refs.input.focus(); // ❌ 报错
// ✅ 等待 DOM 更新后操作
this.$nextTick(() => {
console.log(this.$refs.input); // ✅ <input>
this.$refs.input.focus();
});
},
async handleClickAsync() {
this.showInput = true;
await this.$nextTick(); // Promise 形式
this.$refs.input.focus();
}
}
};
// watch 的回调默认在 DOM 更新后触发,本质上就是用 nextTick 实现的
watch: {
count: {
handler(newVal) {
// 此时 DOM 已更新
this.$refs.chart.update();
}
}
}
// 等价于
watch: {
count: {
handler(newVal) {
this.$nextTick(() => {
this.$refs.chart.update();
});
}
}
}
// Vue 2:通过实例方法
this.$nextTick(() => { /* ... */ });
// Vue 3:从 vue 导入
import { nextTick } from'vue';
nextTick(() => { /* ... */ });
// 或
await nextTick();
Vue 3 中 nextTick 是独立函数,不再绑定在实例上。底层实现思路相同,但更易于测试和复用。
// ❌ 不必要的 nextTick
methods: {
asyn chandle() {
await this.$nextTick(); // 没必要
this.otherData = 'value';
}
}
// ✅ nextTick 只用于"数据变化后立即读 DOM"的同步问题
methods: {
async handle() {
this.showInput = true;
await this.$nextTick();
this.$refs.input.focus(); // 必须等 DOM 更新
}
}
Vue 3 Composition API 的设计理念、与 Options API 的对比、逻辑复用方式、以及迁移策略。
Composition API(组合式 API) 是 Vue 3 引入的新特性,提供了一种更灵活的方式来组织组件逻辑。它不是对 Options API 的替代,而是补充,两者可以在同一个项目中共存。
核心差异
Options API 按"选项类型"组织代码:data、methods、computed、watch、mounted 等选项分别写在不同位置。逻辑分散:一个功能的代码可能跨越多个选项块。
Composition API 按"逻辑功能"组织代码:把同一个功能的 data、computed、watch、生命周期都写在一起。逻辑集中:相关代码一目了然。
为什么需要 Composition API
Options API 在简单组件中很清晰,但当组件变复杂时会暴露问题:
一个组件实现"搜索 + 分页 + 排序"三个功能,代码会散落在 data、methods、computed、watch 中。
多个组件有类似功能时(如分页逻辑),Options API 时代用 mixins 复用,但 mixins 有命名冲突、来源不清、无法传参等缺陷。
Composition API 通过"按功能组织 + 可复用的 Composable 函数"解决这两个问题。
Options API 写法(传统方式)
data 中定义 count、userName、userId 等所有数据。
computed 中定义 doubleCount 等派生属性。
methods 中定义 increment、decrement、fetchUser 等所有方法。
watch 中定义对 count 的监听。
mounted 中调用 fetchUser。
同一个功能(如"用户管理")的代码分散在 5 个不同位置。
Composition API 写法(现代方式)
顶部导入需要的 API:import { ref, computed, watch, onMounted } from 'vue'。
引入自定义的 Composable 函数:useCounter()、useUser()。
数据、计算、监听、生命周期钩子按功能顺序写在一起。
同一个功能的代码集中在一处,便于理解和维护。
setup 语法糖的优势
Vue 3.2+ 引入了 <script setup> 语法,进一步简化 Composition API:
自动暴露:在 <script setup> 中定义的变量、函数自动暴露给模板,不需要 return。
自动注册组件:import 的组件自动在模板中可用,不需要 components 选项。
编译时优化:编译器自动分析模板依赖,未使用的变量不会出现在打包结果中。
更简洁的类型推断:在 TypeScript 中表现更好。
defineProps / defineEmits:在 setup 中通过编译器宏声明 props 和 emits。
顶层 await:可以在 setup 中直接用 await,组件自动等待。
Composable 函数(逻辑复用)
Composition API 的最大价值之一是"逻辑复用"。可以把通用的逻辑(如分页、搜索、用户信息、表单验证)抽成独立的 Composable 函数(也叫 Hook),在其他组件中直接调用。
Composable 函数的特点:
命名以 use 开头(约定),如 usePagination、useSearch。
内部使用响应式 API 维护状态。
返回需要暴露的状态和方法(对象或数组)。
可以接受参数(如 usePagination({ pageSize: 20 }))。
没有命名冲突问题:返回什么用什么的解构,函数内部变量不污染调用方。
Options API vs Composition API 对比
逻辑组织:Options API 按选项类型分散,Composition API 按逻辑功能集中。
代码复用:Options API 用 mixins(有缺陷),Composition API 用 Composables(清晰可控)。
类型推断:Composition API 更好,TypeScript 友好。
学习成本:Options API 更低,新手友好;Composition API 略高但功能强。
适合场景:Options API 适合小型简单组件,Composition API 适合复杂组件和大型应用。
实际项目:复杂表单场景对比
以一个复杂表单(基础信息 + 地址信息 + 验证 + 提交)为例:
Options API:所有字段铺在 data 中,验证函数散落在 methods,watch 监听 5 个字段变化,代码量爆炸且维护困难。
Composition API:把验证逻辑抽成 useFormValidation、地址信息抽成 useAddress,主组件 setup 中只做"组装"和"提交",代码清晰且可复用。
迁移策略
新组件:直接用 <script setup> + Composition API。
简单组件:保留 Options API 也无妨,两者可以共存。
复杂组件:逐步迁移到 Composition API,享受更好的复用性。
项目统一:建议新项目直接用 Composition API,老项目看情况逐步迁移。
export default {
beforeCreate() {
console.log('beforeCreate'); // ② 后执行
console.log(this); // ✅ 已经是指向组件实例
},
setup(props, context) {
console.log('setup'); // ① 先执行
console.log(this); // ❌ undefined:组件实例还没创建
return { msg: 'hello' };
}
};
setup 接收两个参数:
props:组件的 props(响应式的)。
context:包含 attrs、slots、emit、expose。
<script setup>
// 1. defineProps 声明 props
const props = defineProps({
title: { type: String, required: true },
count: { type: Number, default: 0 }
});
// 2. defineEmits 声明事件
const emit = defineEmits(['change', 'delete']);
// 3. defineExpose 显式暴露(默认 setup 内部不暴露给外部)
defineExpose({ reset, validate });
</script>
这些是编译器宏,不需要 import,编译时由 Vue 编译器处理。
// 阶段1:mixins(Vue 2)
const commonMixin= {
data() { return { loading: false }; },
methods: {
async fetchData() { /* ... */ }
}
};
// ❌ 问题:命名冲突、来源不清、无法传参
export default {
mixins: [commonMixin]
};
// 阶段2:Composable(Vue 3)
export function useFetch(url) {
const data = ref(null);
const loading = ref(false);
async function fetchData() {
loading.value = true;
const res = await fetch(url);
data.value = await res.json();
loading.value = false;
}
return { data, loading, fetchData };
}
// ✅ 优势:来源清晰、可传参、无命名冲突
export default {
setup() {
const { data, loading, fetchData } = useFetch('/api/users');
return { data, loading, fetchData };
}
};
// composables/usePagination.js
import { ref, computed } from'vue';
export function usePagination(fetchFn, options= {}) {
const { pageSize=10 } =options;
const currentPage = ref(1);
const total = ref(0);
const list = ref([]);
const loading = ref(false);
const totalPages = computed(() =>Math.ceil(total.value/pageSize));
async function load() {
loading.value=true;
try {
const res = await fetchFn({
page: currentPage.value,
pageSize
});
list.value = res.list;
total.value = res.total;
} finally {
loading.value = false;
}
}
function nextPage() {
if (currentPage.value < totalPages.value) {
currentPage.value++;
load();
}
}
function prevPage() {
if (currentPage.value>1) {
currentPage.value--;
load();
}
}
return {
currentPage, totalPages, list, loading,
load, nextPage, prevPage
};
}
<!-- UserList.vue -->
<script setup>
import { usePagination } from'@/composables/usePagination';
import { fetchUsers } from'@/api/user';
const { currentPage, totalPages, list, loading, load, nextPage, prevPage }
=usePagination(fetchUsers, { pageSize: 20 });
load();
</script>
import { ref, reactive } from'vue';
// ✅ 基本类型用 ref
const count = ref(0);
const message = ref('hello');
// ✅ 对象用 reactive(属性访问更简洁)
const form = reactive({
username: '',
password: '',
remember: false
});
// ⚠️ reactive 整体替换会丢失引用
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // ❌ 丢失响应式!
// ✅ 需要整体替换时用 ref
const stateRef = ref({ count: 0 });
stateRef.value = { count: 1 }; // ✅ 保持响应式
import { reactive, toRefs } from'vue';
const state = reactive({ name: '张三', age: 25 });
// ❌ 解构后丢失响应式
const { name, age } = state;
name = '李四'; // 不会触发更新!
// ✅ 用 toRefs 转换
const { name, age } = toRefs(state);
name.value = '李四'; // ✅ 触发更新
Composition API 在性能上和 Options API 持平(底层响应式系统是同一套)。Composition API 的优势不在性能,而在代码组织和复用。
前面几个章节,已经把vue基础题的10道题的知识点和答案讲解完毕,接下来,进入考试 and 复习环节
1.1 Vue 2 使用 Object.__ 实现响应式,无法监听 __(新增/删除)属性;Vue 3 使用 __ 重写了响应式系统,可原生支持新增属性。
1.2 Vue 2 中,数组通过 __ 赋值无法触发响应式,必须使用 this.__;Vue 3 使用 __,数组索引赋值也能触发响应式。
1.3 Vue 的 DOM 更新是 __(同步/异步)操作,数据变化后不会立即更新 DOM,而是放入队列,nextTick 正是用来 __(等待/立即执行)DOM 更新完成。
参考答案
1.1defineProperty,新增或删除,Proxy
1.2直接通过索引,$set,Proxy
1.3异步,等待
2.1 MVVM 模式中,__(Model/View/ViewModel)层代表用户看到的界面;__(Model/View/ViewModel)层持有视图的状态和数据逻辑;__(Model/View/ViewModel)层代表原始数据。
2.2 Vue 中的 data() 函数对应 MVVM 的 __(View/ViewModel/Model)层;模板对应 __(View/ViewModel/Model)层;Vue 实例本身对应 __(View/ViewModel/Model)层。
参考答案
2.1View,ViewModel,Model
2.2Model,View,ViewModel
3.1created 生命周期钩子在 data、computed、methods 可用之后执行,可以进行 __(DOM 操作/数据请求);mounted 钩子可以访问 $refs 和进行 __(数据请求/DOM 操作)。
3.2computed 属性必须返回 __(有/无)返回值,且会 __(缓存/不缓存)计算结果;watch 适合处理 __(同步计算/异步副作用)。
3.3 组件卸载前需要清理定时器、事件监听器,否则会造成 __(内存泄漏/渲染阻塞)。
参考答案
3.1数据请求,DOM 操作
3.2有,缓存,异步副作用
3.3内存泄漏
4.1v-if 是真正的 __(条件渲染/样式切换),条件为 false 时 DOM 节点 __(存在/不存在);v-show 是通过 __(display:none/删除 DOM)控制显示隐藏。
4.2 频繁切换的场景适合用 __(v-if/v-show);初始条件很可能为 false 的场景适合用 __(v-if/v-show)。
4.3v-show__(支持/不支持)v-else;v-if__(支持/不支持)v-else。
参考答案
4.1条件渲染,不存在,display:none
4.2v-show,v-if
4.3不支持,支持
5.1 Vue 2 中 v-for 优先级高于 v-if,导致 __(每次遍历都要判断/先判断再遍历),性能浪费;Vue 3 中 v-if 优先级更高,但两者混用仍 __(推荐/不推荐)。
5.2 替代方案一:使用 __ 属性预先过滤数据,推荐用于数据本身不经常变化的场景;替代方案二:使用 v-if 作为 __(子元素/父容器)元素,控制整个列表的渲染。
5.3 如果列表很大但需要保留状态切换,用 __(v-if/v-show)替代内层 v-if。
参考答案
5.1每次遍历都要判断,不推荐
5.2computed,父容器
5.3v-show
6.1 父组件向子组件传递数据用 __(props/emit);子组件向父组件传递数据用 __(props/emit)。
6.2v-model 本质是 :modelValue + __ 的语法糖;在 Vue 3 中使用 __(emit/defineEmits)声明子组件触发的事件。
6.3 Vue 3 支持 v-model:propName 绑定 __(多个/单个)值,实现多个 v-model 双向绑定。
参考答案
6.1props,emit
6.2@update:modelValue,defineEmits
6.3多个
7.1 插槽允许父组件向子组件的 __(指定位置/任意位置)插入 HTML 内容;子组件通过 __(slot/slots)标签定义插槽区域。
7.2 插槽可以传递数据给父组件,这种插槽叫做 __(普通插槽/作用域插槽);Vue 2 中用 __(slot/v-slot)获取作用域数据。
7.3 Vue 3 中 <template #header="{ item }"> 是 v-slot:header="__" 的简写形式,支持 __(动态/静态)插槽名如 #[dynamicName]。
参考答案
7.1指定位置,slot
7.2作用域插槽,slot-scope
7.3{ item },动态
8.1computed 基于 __(依赖缓存/每次重算),依赖不变不会重算;watch 每次数据变化都会执行,__(有/无)缓存机制。
8.2computed 必须 __(有/无)返回值且必须是同步的;watch 适合处理 __(同步计算/异步操作/副作用)。
8.3watch 的 deep: true 用于 __(深度监听/立即执行);immediate: true 用于 __(深度监听/组件创建时立即执行一次)。
参考答案
8.1依赖缓存,无
8.2有,异步操作/副作用
8.3深度监听,组件创建时立即执行一次
9.1 Vue 的 DOM 更新是 __(同步/异步)的,响应式数据变化后不会立即更新 DOM,而是将更新放入队列,在下一个 tick __(立即/批量)执行。
9.2nextTick 内部实现会优先使用 __(Promise/setTimeout)微任务,在不支持的环境中降级为宏任务。
9.3 典型使用场景:在 v-if 切换为 true 之后调用 $refs,或在异步数据更新后操作 DOM,此时需要用 nextTick 确保 __(代码执行/DOM 更新)在 DOM 更新之后。
参考答案
9.1异步,批量
9.2Promise
9.3代码执行,DOM 更新
10.1 Composition API 的 <script setup> 语法糖中,变量和函数 __(需要/不需要)手动 return,自动暴露给模板;使用 __(mixins/Composables)实现逻辑复用,避免命名冲突。
10.2setup() 函数在 __(beforeCreate/created)之前执行;onMounted() 等生命周期钩子需要 __(从 vue 导入/全局注册)才能使用。
10.3 Composition API 相比 Options API 的优势包括:更好的 __(类型推断/性能)、按 __(选项类型/逻辑功能)组织代码、更清晰的逻辑 __(复用/隔离)。
参考答案
10.1不需要,Composables
10.2beforeCreate,从 vue 导入
10.3类型推断,逻辑功能,复用