💚
Vue
11 道题目
难度筛选:
Vue 2:Object.defineProperty
• 劫持对象属性的 getter/setter
• 缺点:无法检测属性的新增/删除、数组索引直接赋值
Vue 3:Proxy
• 代理整个对象,而非单个属性
• 能检测属性新增/删除
• 能检测数组变化
• 性能更好(懒代理,访问时才代理嵌套对象)
• 劫持对象属性的 getter/setter
• 缺点:无法检测属性的新增/删除、数组索引直接赋值
Vue 3:Proxy
• 代理整个对象,而非单个属性
• 能检测属性新增/删除
• 能检测数组变化
• 性能更好(懒代理,访问时才代理嵌套对象)
const reactive = (obj) => new Proxy(obj, {
get(target, key) { track(target, key); return target[key]; },
set(target, key, val) { target[key] = val; trigger(target, key); return true; }
}); 查看答案即标记为已答
1. Props / $emit:父子组件通信
2. provide / inject:跨层级传递(祖先 → 后代)
3. EventBus(Vue 3 用 mitt):任意组件通信
4. Vuex / Pinia:全局状态管理
5. $refs:父组件直接访问子组件
6. $attrs / $listeners:属性透传
推荐:
• 父子:Props + Emit
• 跨层级:provide/inject
• 复杂项目:Pinia
2. provide / inject:跨层级传递(祖先 → 后代)
3. EventBus(Vue 3 用 mitt):任意组件通信
4. Vuex / Pinia:全局状态管理
5. $refs:父组件直接访问子组件
6. $attrs / $listeners:属性透传
推荐:
• 父子:Props + Emit
• 跨层级:provide/inject
• 复杂项目:Pinia
查看答案即标记为已答
computed(计算属性):
• 有缓存,依赖不变不会重新计算
• 必须有返回值
• 适合派生数据
watch(侦听器):
• 无缓存,每次变化都执行
• 可以执行异步操作
• 适合副作用(请求、DOM 操作等)
原则:需要新值用 computed,需要执行操作用 watch。
• 有缓存,依赖不变不会重新计算
• 必须有返回值
• 适合派生数据
const fullName = computed(() => firstName.value + ' ' + lastName.value)watch(侦听器):
• 无缓存,每次变化都执行
• 可以执行异步操作
• 适合副作用(请求、DOM 操作等)
watch(keyword, (val) => { search(val) })原则:需要新值用 computed,需要执行操作用 watch。
查看答案即标记为已答
选项式 API(Options API):
逻辑分散在 data/methods/computed 等选项中。
组合式 API(Composition API):
相关逻辑可以组织在一起,更好的代码复用和类型推导。
推荐:新项目使用
export default {
data() { return { count: 0 } },
methods: { increment() { this.count++ } },
mounted() { ... }
}逻辑分散在 data/methods/computed 等选项中。
组合式 API(Composition API):
setup() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}相关逻辑可以组织在一起,更好的代码复用和类型推导。
推荐:新项目使用
<script setup> 语法糖,最简洁。 查看答案即标记为已答
作用:在 DOM 更新完成后执行回调。
原理:
1. Vue 的数据更新是异步的(批处理)
2. 修改响应式数据后,组件不会立即重新渲染
3. 而是在"下一个事件循环"中批量更新
4.
实现方式:
优先使用
降级到
再降级到
最后降级到
使用场景:修改数据后需要读取更新后的 DOM。
原理:
1. Vue 的数据更新是异步的(批处理)
2. 修改响应式数据后,组件不会立即重新渲染
3. 而是在"下一个事件循环"中批量更新
4.
nextTick 将回调放入微任务队列,在 DOM 更新后执行实现方式:
优先使用
Promise.then()(微任务)降级到
MutationObserver再降级到
setImmediate最后降级到
setTimeout(fn, 0)使用场景:修改数据后需要读取更新后的 DOM。
查看答案即标记为已答
全局守卫:
•
•
•
路由独享守卫:
•
组件内守卫:
•
•
•
执行顺序:
beforeEach → beforeRouteUpdate → beforeEnter → beforeRouteEnter → beforeResolve → afterEach
典型应用:
• 登录权限校验
• 页面标题设置
• 数据预加载
• 离开确认(表单未保存)
•
router.beforeEach((to, from, next) => {}) — 全局前置•
router.beforeResolve() — 解析守卫(组件内守卫之后)•
router.afterEach((to, from) => {}) — 全局后置(无 next)路由独享守卫:
•
beforeEnter: (to, from, next) => {} — 定义在路由配置上组件内守卫:
•
onBeforeRouteEnter — 进入前(无法访问 this)•
onBeforeRouteUpdate — 路由参数变化时•
onBeforeRouteLeave — 离开前(用于表单未保存提醒)执行顺序:
beforeEach → beforeRouteUpdate → beforeEnter → beforeRouteEnter → beforeResolve → afterEach
典型应用:
• 登录权限校验
• 页面标题设置
• 数据预加载
• 离开确认(表单未保存)
查看答案即标记为已答
| Vuex | Pinia | |
|---|---|---|
| 模块化 | modules + namespaced | 天然独立 Store |
| Mutations | 必须通过 mutation 修改 | 直接修改或通过 action |
| TypeScript | 支持差,需大量类型声明 | 原生 TS 支持,自动推导 |
| 体积 | 较大 | ~1KB |
| SSR | 需额外处理 | 内置支持 |
| DevTools | ✅ | ✅ |
Pinia 使用示例:
// stores/counter.ts
export const useCounterStore = defineStore('counter', () => {
const count = ref(0); // state
const double = computed(() => count.value * 2); // getter
function increment() { count.value++; } // action
return { count, double, increment };
});Pinia 优势:
• 更简洁的 API(Setup Store 风格)
• 不需要 mutations,actions 支持异步
• 天然 tree-shakable
• Vue 官方推荐(Vue 3 默认状态管理方案)
查看答案即标记为已答
Teleport(传送门):将组件内容渲染到 DOM 中的其他位置。
解决的问题:
• 弹窗/抽屉/通知 不受父组件 z-index 和 overflow 影响
• 避免 CSS 层叠上下文问题
• 逻辑上仍属于当前组件(事件、props 正常工作)
Suspense(实验性):等待异步组件加载完成时显示 fallback。
触发 Suspense 的场景:
•
•
<Teleport to="body">
<div class="modal">弹窗内容</div>
</Teleport>解决的问题:
• 弹窗/抽屉/通知 不受父组件 z-index 和 overflow 影响
• 避免 CSS 层叠上下文问题
• 逻辑上仍属于当前组件(事件、props 正常工作)
Suspense(实验性):等待异步组件加载完成时显示 fallback。
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<Spinner />
</template>
</Suspense>触发 Suspense 的场景:
•
defineAsyncComponent 异步组件•
<script setup> 中使用顶层 await 查看答案即标记为已答
keep-alive:缓存组件实例,避免重复创建和销毁,保留组件状态。
属性:
•
•
•
生命周期钩子:
•
•
典型场景:
• 列表页 → 详情页 → 返回列表页,保留滚动位置和筛选条件
• 多 Tab 切换保留各 Tab 状态
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>属性:
•
include — 只有匹配的组件会被缓存•
exclude — 匹配的组件不会被缓存•
max — 最大缓存实例数(LRU 策略淘汰)<keep-alive :include="['Home', 'Profile']" :max="10">生命周期钩子:
•
onActivated — 组件被激活(从缓存恢复)时调用•
onDeactivated — 组件被停用(进入缓存)时调用典型场景:
• 列表页 → 详情页 → 返回列表页,保留滚动位置和筛选条件
• 多 Tab 切换保留各 Tab 状态
查看答案即标记为已答
Vue 3 虚拟 DOM:用 JS 对象描述真实 DOM 结构,通过 Diff 算法计算最小更新。
Vue 3 Diff 优化 vs Vue 2:
1. 静态提升(Static Hoisting):静态节点只创建一次,每次更新直接复用
2. PatchFlag(补丁标记):编译时标记动态内容,Diff 时只比较动态部分
3. Block Tree:以 Block 为单位收集动态节点,扁平化遍历
4. 缓存事件处理函数:避免每次渲染创建新函数
核心 Diff 算法(最长递增子序列):
1. 从头比对,相同则复用
2. 从尾比对,相同则复用
3. 新旧队列交叉比对
4. 通过 最长递增子序列(LIS)最小化 DOM 移动
示例 PatchFlag:
PatchFlag = 1 表示只有文本内容是动态的,Diff 时跳过其他属性。
Vue 3 Diff 优化 vs Vue 2:
1. 静态提升(Static Hoisting):静态节点只创建一次,每次更新直接复用
2. PatchFlag(补丁标记):编译时标记动态内容,Diff 时只比较动态部分
3. Block Tree:以 Block 为单位收集动态节点,扁平化遍历
4. 缓存事件处理函数:避免每次渲染创建新函数
核心 Diff 算法(最长递增子序列):
1. 从头比对,相同则复用
2. 从尾比对,相同则复用
3. 新旧队列交叉比对
4. 通过 最长递增子序列(LIS)最小化 DOM 移动
示例 PatchFlag:
// 模板 <div>{{ msg }}</div>
// 编译结果
_createElementVNode("div", null, _toDisplayString(msg), 1 /* TEXT */)PatchFlag = 1 表示只有文本内容是动态的,Diff 时跳过其他属性。
查看答案即标记为已答
Vue 3 编译器对模板进行了深度优化:
1. 静态提升(Static Hoisting):
不会变化的节点提升到渲染函数外,只创建一次。
2. PatchFlag 补丁标记:
编译时分析节点动态部分,Diff 时只比较标记部分。
• TEXT = 1, CLASS = 2, STYLE = 4, PROPS = 8 ...
3. Block Tree(块级树):
v-if/v-for 会创建新的 Block,每个 Block 扁平收集内部动态节点。
Diff 时不需要递归遍历整棵树。
4. 静态字符串化:
连续 20+ 个静态节点直接编译为字符串,比虚拟 DOM 创建更快。
效果:整体渲染性能比 Vue 2 提升 1.3~2 倍,内存减少一半。
1. 静态提升(Static Hoisting):
不会变化的节点提升到渲染函数外,只创建一次。
// 之前:每次渲染都创建
return h('div', null, [h('span', null, '静态文本'), h('p', null, msg)])
// 优化后:静态只创建一次
const _hoisted = h('span', null, '静态文本')
return h('div', null, [_hoisted, h('p', null, msg)])2. PatchFlag 补丁标记:
编译时分析节点动态部分,Diff 时只比较标记部分。
• TEXT = 1, CLASS = 2, STYLE = 4, PROPS = 8 ...
3. Block Tree(块级树):
v-if/v-for 会创建新的 Block,每个 Block 扁平收集内部动态节点。
Diff 时不需要递归遍历整棵树。
4. 静态字符串化:
连续 20+ 个静态节点直接编译为字符串,比虚拟 DOM 创建更快。
效果:整体渲染性能比 Vue 2 提升 1.3~2 倍,内存减少一半。
查看答案即标记为已答