梳理这份 Vue 面试题,初衷是为了帮助自己系统复习,查漏补缺,同时也希望能给正在准备前端面试的小伙伴们一些参考和启发!
1.你对vue的理解到什么程度?
对 Vue 的理解可以总结为以下几个方面,涵盖核心概念、高级特性和实际应用场景
1.1.核心机制理解
- 响应式系统 :理解基于
Object.defineProperty
(Vue 2)和Proxy
(Vue 3)的依赖追踪与触发更新机制,包括依赖收集(Dep/Watcher)和异步批量更新(nextTick)。 - 虚拟 DOM 与渲染 :了解模板编译为渲染函数、虚拟 DOM 的 diff/patch 过程,以及
key
的作用。 - 组件化 :掌握组件生命周期、父子通信(
props
/$emit
)、跨组件通信(provide/inject、事件总线、Vuex/Pinia)。
1.2. Composition API 深度掌握
- 熟练使用
ref
、reactive
、computed
、watch
等响应式 API,理解其与 Options API 的差异。 - 能够合理组织逻辑复用(自定义组合式函数),避免 React Hooks 的闭包陷阱问题。
- 了解
<script setup>
语法糖和编译器优化(如静态提升)。
1.3. 状态管理方案
- Vuex:理解模块化、严格模式、插件机制。
- Pinia:熟悉基于 Composition API 的设计、类型推导优势,以及持久化等插件生态。
- 能够根据场景选择轻量级(共享状态)或集中式方案。
1.4. 性能优化实践
- 组件级优化:
v-once
、v-memo
、懒加载(defineAsyncComponent
)。 - 列表渲染:键策略与避免 v-if/v-for 混用。
- 代码分割:路由懒加载(
import()
+ Webpack/Vite)。 - 响应式数据精细化控制(如
shallowRef
避免深层响应)。
1.5. 生态工具链
- 构建工具 :Vite 的快速启动与 HMR 原理,配置 Vue 项目(如
@vitejs/plugin-vue
)。 - 路由:Vue Router 的动态路由、导航守卫、滚动行为控制。
- 服务端渲染:Nuxt.js 的约定式开发、hydration 过程及 SEO 优化。
1.6. 原理层探究
- 能解释模板如何编译为渲染函数(如
vue-template-compiler
)。 - 了解响应式系统的局限性(如数组/对象变更检测的注意事项)。
- 熟悉自定义指令、插件开发等扩展机制。
1.7. 实战经验
- 复杂表单处理(表单校验、动态表单)。
- 权限控制(路由守卫、动态菜单)。
- 与 TypeScript 深度集成(类型化
props
、emits
、Pinia Store)。
2.vue为什么要求组件模板只能有一个根元素?
Vue 2.x 要求组件模板必须有单个根元素 ,这一限制源于其虚拟 DOM 的 diff 算法设计。不过 Vue 3 通过引入 Fragment(片段) 支持了多根节点模板。以下是具体原因和演进过程:
Vue 2 单根元素的原因
-
虚拟 DOM 的 Patch 机制
Vue 的响应式更新需要比较新旧虚拟 DOM 树(diff 算法),而 diff 算法需要明确的父子关系作为比较锚点。单根元素能确保:
- 组件始终有一个确定的入口节点用于挂载和比对
- 父组件通过
$el
属性能直接访问子组件的根 DOM
-
模板编译的确定性
模板会被编译为渲染函数,单根节点使编译结果更可预测:
javascript// 编译后的渲染函数需要返回单个 VNode render(h) { return h('div', {}, [/* 子节点 */]) // 必须单一根节点 }
-
与 HTML 解析兼容
浏览器解析 HTML 时,若模板有多个并列根元素(如
<div></div><span></span>
),实际会被包裹在隐式父级中,可能导致意外行为。
Vue 3 的多根节点支持
Vue 3 通过 Fragment 特性允许模板多根节点:
vue
<template>
<div>Node 1</div>
<div>Node 2</div> <!-- 合法 -->
</template>
实现原理:
- 编译时自动将多根节点包裹在虚拟的
Fragment
节点中 Fragment
不会渲染为实际 DOM,仅作为逻辑容器- 父组件通过
$el
会指向多根节点中的第一个 DOM 元素
为什么 Vue 2 不这样设计?
- 性能权衡
Vue 2 的虚拟 DOM 实现更简单高效,单根节点减少边缘情况处理。 - 渐进式演进
Vue 3 重写了虚拟 DOM,引入Fragment
等新特性以适配更复杂场景。
实际影响
场景 | Vue 2 | Vue 3 |
---|---|---|
多根节点模板 | ❌ 报错 | ✅ 支持 |
$el 指向 |
根元素 | 第一个节点 |
递归组件引用自身 | 必须通过单根包裹 | 可直接多根 |
最佳实践建议
- Vue 2 项目 :用
<div>
或<transition>
包裹多节点 - Vue 3 项目 :
- 多根节点时注意样式作用域(可能需改用
:deep()
) - 需要访问根元素时,使用
ref
替代$el
- 多根节点时注意样式作用域(可能需改用
3.什么是diff算法?
Diff 算法是虚拟 DOM 的核心,用于对比新旧虚拟 DOM 树的差异,计算出最小的 DOM 操作。
1.为什么需要它?
直接操作DOM非常耗性能(比如 innerHTML
全量替换),而 虚拟 DOM(Virtual DOM) 提供了一种更高效的方式:
- 不直接操作真实 DOM(因为 DOM 操作很慢)。
- 先生成虚拟 DOM(JavaScript 对象,轻量级)
- 对比新旧虚拟 DOM(diff 算法)
- 只更新必要的真实 DOM(减少性能开销)
javascript
// 直接操作 DOM(性能差)
document.getElementById('app').innerHTML = newHTML;
// 使用虚拟 DOM + Diff(性能优化)
const oldVNode = createVNode(oldTree);
const newVNode = createVNode(newTree);
const patches = diff(oldVNode, newVNode); // 计算差异
patch(realDOM, patches); // 局部更新
2. 主要策略
- 同级比较:只比较同一层级的节点,不跨层级。
- 双端比较(头头、尾尾、旧头新尾、旧尾新头)。
- 依赖
key
:用于精准识别相同节点(如v-for
列表)
3. 核心流程
双端比较算法
步骤 | 操作 | 目的 |
---|---|---|
① 头头对比 | 旧头 vs 新头 |
处理头部相同节点 |
② 尾尾对比 | 旧尾 vs 新尾 |
处理尾部相同节点 |
③ 旧头 vs 新尾 | 交叉对比 | 检测列表反转情况 |
④ 旧尾 vs 新头 | 交叉对比 | 检测列表反转情况 |
⑤ key 匹配 |
建立索引查找 | 处理中间插入/删除 |
场景 :对比子节点列表 [A,B,C,D]
(旧)和 [D,A,B,E]
(新)
(1)初始化指针
javascript
let oldStartIdx = 0, oldEndIdx = 3; // 旧列表头尾指针
let newStartIdx = 0, newEndIdx = 3; // 新列表头尾指针
(2)双端比较步骤
javascript
// 步骤1:头头对比 (A vs D)
if (oldChildren[oldStartIdx].key === newChildren[newStartIdx].key) {
// 不匹配,进入下一步
}
// 步骤2:尾尾对比 (D vs E)
if (oldChildren[oldEndIdx].key === newChildren[newEndIdx].key) {
// 不匹配,进入下一步
}
// 步骤3:旧头 vs 新尾 (A vs E)
if (oldChildren[oldStartIdx].key === newChildren[newEndIdx].key) {
// 不匹配,进入下一步
}
// 步骤4:旧尾 vs 新头 (D vs D) -> 匹配!
if (oldChildren[oldEndIdx].key === newChildren[newStartIdx].key) {
patchVnode(oldChildren[oldEndIdx], newChildren[newStartIdx]);
// 移动 DOM 节点到新位置
parent.insertBefore(oldChildren[oldEndIdx].elm, oldChildren[oldStartIdx].elm);
oldEndIdx--;
newStartIdx++;
}
// 步骤5:key 映射查找剩余节点
const keyMap = {};
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
keyMap[oldChildren[i].key] = i;
}
图示过程:
ini
旧列表: [A, B, C, D] 新列表: [D, A, B, E]
↑ ↑ ↑ ↑
oldStart oldEnd newStart newEnd
1. D 匹配,移动 D 到最前面
2. 剩余比较 [A,B,C] 和 [A,B,E]
3. 发现 E 是新增,插入
4. Key 的作用
没有 Key 的问题:
javascript
// 旧列表: [A, B, C]
// 新列表: [B, A, C]
// 没有 key 时,可能误判 B 是修改后的 A
正确使用 Key:
html
<!-- Vue 模板 -->
<template>
<div v-for="item in list" :key="item.id">
{{ item.text }}
</div>
</template>
javascript
// 虚拟 DOM 结构
{
tag: 'div',
key: item.id, // 通过 key 精准匹配
children: item.text
}
5. Vue 3 优化
静态提升(Hoist Static):
javascript
// 编译前
<template>
<div>Hello</div> <!-- 静态节点 -->
<div>{{ msg }}</div>
</template>
// 编译后(静态节点被提升)
const _hoisted = createVNode('div', null, 'Hello');
function render() {
return [_hoisted, createVNode('div', null, ctx.msg)];
}
Block Tree 动态追踪:
javascript
// 编译时标记动态节点
const block = {
dynamicChildren: [
{ tag: 'div', props: { id: ctx.id } } // 只追踪动态部分
]
}
6. 完整 Diff 示例
javascript
function updateChildren(parentElm, oldCh, newCh) {
let oldStartIdx = 0, newStartIdx = 0;
let oldEndIdx = oldCh.length - 1, newEndIdx = newCh.length - 1;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// 双端比较逻辑...
}
// 处理剩余节点
if (oldStartIdx > oldEndIdx) {
// 新增节点
addVnodes(parentElm, newCh[newStartIdx]);
} else {
// 删除旧节点
removeVnodes(parentElm, oldCh[oldStartIdx]);
}
}
7.示例对比
场景:列表更新
Vue 2 的处理:
- 全量比对新旧列表。
- 依赖
key
查找可复用节点。 - 移动或新增/删除 DOM。
Vue 3 的处理:
- 通过 Block Tree 直接定位动态节点。
- 静态部分跳过比对。
- 仅更新变化的 DOM。
8.总结对比表
特性 | Vue 2 | Vue 3 |
---|---|---|
Diff 策略 | 双端比较 + 全量比对 | 双端比较 + 静态标记 + Block Tree (动态追踪) |
多根节点 | ❌ 不支持 | ✅ Fragment 支持 |
静态节点处理 | 参与 Diff | ✅ 静态提升(不参与 Diff) |
动态节点优化 | ❌ 无 | ✅ Patch Flag 标记动态属性 |
性能 | (较慢)全量比对 | 更快(精准比对) |
Key 必要性 | 必需 | 必需(但优化了复用逻辑) |
4. watch和created哪个先执行?为什么?
在 Vue 的生命周期中,created
钩子会先于 watch
执行。这是因为:
-
初始化阶段:
- Vue 首先初始化数据(
data
、props
等)。 - 然后调用
created
钩子,此时组件实例已创建完成,但 DOM 还未生成。
- Vue 首先初始化数据(
-
watch 设置阶段:
- 在数据初始化完成后,Vue 才会设置响应式系统和
watch
监听器。 watch
的首次触发是在数据变化后,而created
是同步执行的钩子。
- 在数据初始化完成后,Vue 才会设置响应式系统和
示例代码:
javascript
export default {
data() {
return { count: 0 };
},
created() {
console.log("created 执行"); // 1. 先执行
this.count = 1; // 修改数据
},
watch: {
count() {
console.log("watch 触发"); // 2. 后执行
},
},
};
5. mixins和extends有什么区别?
特性 | mixins | extends |
---|---|---|
数量 | 可混入多个 | 只能继承一个 |
合并策略 | 选项合并(相同选项合并为数组) | 类似 mixins,但优先级更高 |
用途 | 功能复用 | 扩展基础组件 |
执行顺序 | 后定义的 mixin 先执行 | 在 mixins 之前执行 |
组件优先级 | 低于组件自身选项 | 高于 mixins,但低于组件自身选项 |
示例:
javascript
// extends 示例
const BaseComponent = { methods: { log() { console.log("base"); } } };
// mixins 示例
const myMixin = { methods: { log() { console.log("mixin"); } } };
export default {
extends: BaseComponent,
mixins: [myMixin],
methods: {
log() {
console.log("component");
// 调用优先级:component > extends > mixins
},
},
};
6. mixins有什么使用场景?
- 跨组件共享逻辑 :
- 表单验证、数据获取等通用逻辑。
- 功能注入 :
- 日志记录、埋点统计等全局功能。
- UI 行为模式 :
- 拖拽、无限滚动等可复用的交互行为。
最佳实践:
- 保持单一职责,避免复杂依赖。
- 使用命名前缀防止属性冲突(如
$_mixinName
)。
7. created与activated有什么区别?
钩子 | 调用时机 | 调用次数 | 适用场景 |
---|---|---|---|
created |
组件实例创建完成后同步调用 | 1 次 | 初始化数据、API 调用 |
activated |
被 <keep-alive> 缓存的组件激活时 |
多次 | 恢复定时器、刷新数据 |
示例:
javascript
export default {
created() {
console.log("组件创建"); // 仅执行一次
},
activated() {
console.log("组件激活"); // 每次从缓存恢复时触发
},
};
8. 如何引入异步组件?
方法 1:工厂函数
javascript
components: {
AsyncComp: () => import("./AsyncComp.vue"),
}
方法 2:高级异步组件(支持 Loading/Error 状态)
javascript
const AsyncComp = () => ({
component: import("./AsyncComp.vue"),
loading: LoadingComp,
error: ErrorComp,
delay: 200, // 延迟显示 Loading
timeout: 3000, // 超时时间
});
路由懒加载:
javascript
const router = new VueRouter({
routes: [{ path: "/async", component: () => import("./AsyncRoute.vue") }],
});
9. 在vue项目中scss scoped穿透符,无效的解决方案有哪些?
问题 :
在 scoped
样式中,>>>
在 Sass/SCSS 中可能失效。
解决方案:
-
使用
::v-deep
或/deep/
:scss.parent ::v-deep .child { color: red; }
-
全局样式(不加
scoped
)。 -
CSS Modules:
html<style module> .parent { /* ... */ } .parent .child { /* ... */ } </style>
10. 什么在v-for中的key不推荐使用随机数或者index呢?那要怎么使用才比较好呢?
原因:
- 随机数:每次渲染生成新 key,导致 DOM 无法复用,性能下降。
- index:列表顺序变化时,key 不稳定性可能导致状态错乱(如输入框内容错位)。
推荐做法:
html
<div v-for="item in items" :key="item.id">{{ item.text }}</div>
11. vue-loader在webpack编译流程中的哪个阶段?
作用阶段 :Webpack 的 模块转换阶段 。
功能:
- 解析
.vue
单文件组件。 - 拆解为
template
/script
/style
,分别交给对应 loader 处理。 - 生成可执行的 JavaScript 模块。
配置示例:
javascript
// webpack.config.js
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
options: { /* ... */ },
},
],
},
12. 预渲染和SSR(服务端渲染)有什么区别?
特性 | 预渲染 (Prerendering) | 服务端渲染 (SSR) |
---|---|---|
渲染时机 | 构建时生成静态HTML | 每次请求时服务器动态渲染 |
适用场景 | 静态页面(如官网、博客) | 动态内容(如用户仪表盘) |
SEO支持 | ✅ 完全支持 | ✅ 完全支持 |
实现复杂度 | ⚠️ 简单 | ⚠️ 复杂 |
服务器要求 | 无需Node.js(纯静态托管) | 需Node.js服务器 |
数据实时性 | ❌ 需重新构建更新 | ✅ 请求时实时获取 |
如何选择:
- 纯静态内容 → 预渲染(如
prerender-spa-plugin
) - 用户相关动态数据 → SSR(如
Nuxt.js
)
13. 你有用过预渲染技术吗?怎么做的?
预渲染技术是一种用于提升网页性能和SEO的技术,它在服务器端生成HTML内容,而不是在客户端动态生成。Vue.js支持多种预渲染技术,比如服务器端渲染(SSR)和静态站点生成(SSG) 。
预渲染技术实现方案:
- 静态站点生成(SSG):
bash
# 使用Vue CLI插件
vue add prerender-spa
javascript
// vue.config.js
const PrerenderSPAPlugin = require('prerender-spa-plugin')
module.exports = {
plugins: [
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: ['/', '/about', '/contact'],
renderer: new PrerenderSPAPlugin.PuppeteerRenderer({
renderAfterDocumentEvent: 'render-complete'
})
})
]
}
- 服务端渲染(SSR):
bash
# 使用Nuxt.js框架
npx create-nuxt-app my-ssr-project
javascript
// nuxt.config.js
export default {
ssr: true, // 开启服务端渲染模式
target: 'server' // 默认配置
}
核心区别:
- SSG:构建时生成静态HTML,适合内容不变的页面
- SSR:每次请求时动态渲染内容,适合个性化内容
14. 使用vue如何判断页面是否编辑及编辑页面未保存离开时,给出弹窗提示?
可以通过监听页面的beforeunload
事件来实现。当用户尝试离开页面时,检查是否有未保存的编辑内容,如果有,则弹出提示。
完整解决方案:
javascript
export default {
data() {
return {
isEdited: false, // 页面标记是否编辑
formData: {},
initialData: {}
}
},
created() {
this.initialData = JSON.parse(JSON.stringify(this.formData))
},
watch: {
formData: {
deep: true,
handler() {
this.isEdited = !_.isEqual(this.formData, this.initialData)
}
}
},
beforeRouteLeave(to, from, next) {
if (this.isEdited) {
this.$confirm({
title: '确认离开',
message: '有未保存的更改,确定要离开吗?',
confirmButtonText: '离开',
cancelButtonText: '取消'
}).then(() => {
next() // 确认离开
}).catch(() => {
next(false) // 取消离开
})
} else {
next() // 没有编辑内容直接离开
}
},
mounted() {
window.addEventListener('beforeunload', this.handleWindowClose)
},
methods: {
handleWindowClose(e) {
if (this.isEdited) {
e.preventDefault()
e.returnValue = '您有未保存的更改'
}
}
},
beforeDestroy() {
window.removeEventListener('beforeunload', this.handleWindowClose)
}
}
15. vue的.sync修饰符可以用表达式吗?为什么?
.sync
修饰符不能直接使用表达式。.sync
修饰符是Vue 2.x中用于双向绑定子组件的props的语法糖,它基于v-bind
和v-on
实现。
深度解析:
- 不能使用表达式:
html
<!-- 错误用法 -->
<component :[dynamicProp].sync="value" />
- 根本原因:
.sync
是编译时语法糖,会被转换为固定的update:propName
模式- 表达式无法在编译阶段静态分析
- 底层实现依赖明确的prop名称
- 替代方案:
javascript
<component :prop.sync="value"></component>
// 等价于
<component
:propName="value"
@update:propName="value = $event"
/>
16. v-if和v-show哪个优先级更高?
v-if
的优先级高于v-show
v-if
是条件渲染 ,它会根据条件的真假来决定是否渲染元素。如果条件为false
,元素不会被渲染到DOM中。v-show
是条件显示 ,它会始终渲染元素,只是通过CSS的display
属性来控制元素的显示和隐藏。- 当同时使用
v-if
和v-show
时,v-if
会先决定是否渲染元素,如果v-if
的条件为false
,v-show
不会生效。
17. v-for和v-if哪个优先级更高?
版本对比分析:
Vue版本 | 优先级 | 具体表现 |
---|---|---|
2.x | v-for | 先循环再判断,性能较差 |
3.x | v-if | 先判断再循环,推荐用法 |
最佳实践:
html
<!-- Vue2兼容写法 -->
<template v-for="item in list">
<div v-if="item.isActive" :key="item.id">
{{ item.name }}
</div>
</template>
<!-- Vue3推荐写法 -->
<div v-for="item in activeItems" :key="item.id">
{{ item.name }}
</div>
<script>
computed: {
// 将条件逻辑提前到数据层面,而不是依赖模板中的 v-if
activeItems() {
return this.list.filter(item => item.isActive)
}
}
</script>
18. 如何批量引入组件?
适用场景
- Webpack 项目 :使用
require.context
。 - Vite 项目 :使用
glob
动态导入。 - 少量组件:手动批量注册。
- Vue CLI 项目:使用插件自动导入。
1. 使用 require.context
(适用于 Webpack)
在 components/index.js
中:
javascript
const files = require.context('./', false, /\.vue$/);
const components = files.keys().map(key => ({
name: key.replace(/(\.\/|\.vue)/g, ''),
component: files(key).default
}));
const install = Vue => {
components.forEach(item => Vue.component(item.name, item.component));
};
export default { install };
在 main.js
中:
javascript
import Vue from 'vue';
import Components from './components';
Vue.use(Components);
2. 使用 glob
和动态导入(适用于 Vite)
在 components/index.js
中:
javascript
import { defineAsyncComponent } from 'vue';
const components = import.meta.glob('./**/*.vue', { eager: true });
const install = Vue => {
for (const path in components) {
const name = path.split('/').pop().replace(/\.vue$/, '');
Vue.component(name, defineAsyncComponent(() => components[path]));
}
};
export default { install };
在 main.js
中:
javascript
import Vue from 'vue';
import Components from './components';
Vue.use(Components);
3. 手动批量注册(适用于少量组件)
在 components/index.js
中:
javascript
import Button from './Button.vue';
import Input from './Input.vue';
const install = Vue => {
Vue.component('Button', Button);
Vue.component('Input', Input);
};
export default { install };
在 main.js
中:
javascript
import Vue from 'vue';
import Components from './components';
Vue.use(Components);
4. 使用 Vue CLI 插件(如 vue-cli-plugin-auto-import
)
安装插件:
bash
npm install -D vue-cli-plugin-auto-import
在 vue.config.js
中配置:
javascript
module.exports = {
pluginOptions: {
'auto-import': {
components: true
}
}
};
19. vue的v-for如何倒序输出?
四种实现方式:
- 计算属性(推荐):
javascript
computed: {
reversedItems() {
return [...this.items].reverse()
}
}
- 模板内反转:
html
<div v-for="item in [...items].reverse()" :key="item.id">
- 方法处理:
javascript
methods: {
reverseArray(arr) {
return [...arr].reverse()
}
}
- 后端排序:
javascript
// API请求时添加排序参数
axios.get('/api/items?sort=desc')
20. 如何在全局使用axios的实例呢?
在 Vue 项目中全局使用 Axios 实例是一种常见的做法,可以方便地在各个组件中调用 HTTP 请求,同时也可以统一配置 Axios 的基础路径、拦截器等。以下是实现全局使用 Axios 实例的步骤:
1. 安装 Axios
如果尚未安装 Axios,可以通过以下命令安装:
npm install axios
2. 创建 Axios 实例
在项目中创建一个专门的文件(如 src/axios.js
或 src/api/index.js
),用于配置和导出 Axios 实例。
javascript
// utils/request.js
import axios from 'axios'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
// 请求拦截
service.interceptors.request.use(config => {
config.headers['Authorization'] = getToken()
return config
})
// 响应拦截
service.interceptors.response.use(
response => response.data,
error => {
if (error.response.status === 401) {
router.push('/login')
}
return Promise.reject(error)
}
)
export default service
// main.js
import request from '@/utils/request'
app.config.globalProperties.$http = request
// 组件中使用
this.$http.get('/user/info')
21. v-show指令算是重排吗?
浏览器渲染原理分析:
关于"重排"(Reflow)
"重排"(Reflow)是指浏览器重新计算 DOM 元素的布局的过程。当 DOM 元素的尺寸、位置或其他布局相关的属性发生变化时,浏览器需要重新计算这些元素的布局,从而触发重排。重排是一个相对耗性能的操作,因为它涉及到重新计算页面的布局。
- v-show本质:
css
/* 显示时 该元素重新进入布局,浏览器同样需要重新计算布局 */
display: block;
/* 隐藏时 该元素从布局中移除,浏览器需要重新计算周围元素的布局*/
display: none;
- 重排影响:
- 初次渲染:导致一次完整重排
- 切换显示时:触发相邻元素重排
- 性能消耗:比重绘(repaint)更昂贵
- 优化建议:
- 频繁切换的元素使用v-show
- 首次渲染不需要的元素使用v-if
- 复杂动画考虑使用visibility + opacity
21. axios同时请求多个接口,如果当token过期时,怎么取消后面的请求?
请求生命周期管理:

取消请求解决方案CancelToken
:
- 统一管理所有请求:使用一个数组(或 Map)存储所有进行中的请求及其取消函数
- 拦截 401 响应:在响应拦截器中检测 Token 过期(401 状态码)
- 批量取消请求:当检测到 Token 过期时,遍历并执行所有存储的取消函数
- 清理资源:取消后清空存储的请求队列,避免内存泄漏
javascript
// utils/request.js
import axios from 'axios';
// 存储所有进行中的请求及其取消函数
const pendingRequests = new Map();
// 创建 Axios 实例
const api = axios.create({
baseURL: 'https://api.example.com',
});
// 请求拦截器:添加 Token 和取消机制
api.interceptors.request.use(config => {
// 获取 Token(根据实际存储位置调整)
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 创建取消令牌
const source = axios.CancelToken.source();
config.cancelToken = source.token;
// 生成请求唯一标识(URL + 方法 + 参数)
const requestId = `${config.url}-${JSON.stringify(config.params)}-${config.method}`;
// 存储取消函数
pendingRequests.set(requestId, source.cancel);
// 请求完成后自动移除
config.meta = config.meta || {};
config.meta.requestId = requestId;
return config;
});
// 响应拦截器:处理 Token 过期
api.interceptors.response.use(
response => {
// 请求成功完成,从队列移除
const requestId = response.config.meta?.requestId;
if (requestId) pendingRequests.delete(requestId);
return response;
},
error => {
// 请求失败,从队列移除
const requestId = error.config.meta?.requestId;
if (requestId) pendingRequests.delete(requestId);
// 检测 Token 过期
if (error.response?.status === 401) {
cancelAllRequests('Token expired, canceling pending requests');
// 这里可以添加重定向到登录页等逻辑
window.location.href = '/login';
}
// 如果是主动取消的请求,不报错
if (axios.isCancel(error)) {
return new Promise(() => {}); // 中断 Promise 链
}
return Promise.reject(error);
}
);
// 取消所有进行中的请求
function cancelAllRequests(message) {
pendingRequests.forEach(cancel => {
cancel(message);
});
pendingRequests.clear(); // 清空队列
console.warn(message);
}
// 示例:同时发起多个请求
const requests = [
api.get('/user'),
api.get('/posts'),
api.get('/notifications')
];
Promise.allSettled(requests)
.then(results => {
results.forEach(result => {
if (result.status === 'rejected' && !axios.isCancel(result.reason)) {
// 处理真实错误(非取消导致的错误)
console.error('Request failed:', result.reason.message);
}
});
});
22.从0到1自己构架一个vue项目,说说有哪些步骤、哪些重要插件、目录结构你会怎么组织?
创建项目基础
bash
npm init vue@latest my-vue-project
或使用Vite:
bash
npm create vite@latest my-vue-project --template vue
安装基础依赖
bash
cd my-vue-project
npm install
配置TypeScript(可选)
- 安装TypeScript相关依赖
- 配置
tsconfig.json
配置代码规范和格式化
- ESLint + Prettier
- Stylelint(用于CSS规范)
插件推荐
核心插件
vue-router
:官方路由管理pinia
:状态管理(替代Vuex)axios
:HTTP请求库vite
(或webpack):构建工具
UI组件库(选其一)
Element Plus
/Ant Design Vue
/Vuetify
/Naive UI
开发辅助
unplugin-auto-import
:自动导入APIunplugin-vue-components
:自动导入组件vite-plugin-svg-icons
:SVG图标处理vueuse
:常用组合式API工具集
测试工具
vitest
(或Jest):单元测试cypress
:E2E测试
其他实用插件
dayjs
:日期处理lodash-es
:实用工具库nprogress
:页面加载进度条js-cookie
:Cookie操作
目录结构组织
bash
my-vue-project/
├── public/ # 静态资源(不经过打包处理)
├── src/
│ ├── api/ # API请求相关
│ │ ├── modules/ # 按模块划分的API
│ │ └── index.ts # API统一导出
│ ├── assets/ # 静态资源(会经过打包处理)
│ │ ├── images/ # 图片资源
│ │ ├── svg/ # SVG图标
│ │ └── styles/ # 全局样式
│ ├── components/ # 公共组件
│ │ ├── common/ # 全局通用组件
│ │ └── business/ # 业务通用组件
│ ├── composables/ # 组合式函数
│ ├── directives/ # 自定义指令
│ ├── hooks/ # 自定义hooks
│ ├── layouts/ # 布局组件
│ ├── router/ # 路由配置
│ ├── stores/ # Pinia状态管理
│ ├── utils/ # 工具函数
│ ├── views/ # 页面组件
│ ├── App.vue # 根组件
│ └── main.ts # 应用入口
├── .env # 环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── vite.config.ts # Vite配置
├── tsconfig.json # TypeScript配置
├── package.json
└── README.md
详细配置建议
-
Vite配置优化
- 配置alias路径别名
- 配置proxy代理
- 配置打包优化选项
-
路由配置
- 实现路由懒加载
- 配置路由守卫
- 实现权限路由(如果需要)
-
状态管理
- 按模块划分store
- 配置持久化存储(如需)
-
样式方案
- 选择CSS预处理器(Sass/Less)
- 配置全局变量和mixin
- 考虑CSS Modules或CSS-in-JS方案
-
环境配置
- 区分不同环境变量
- 配置多环境打包脚本
-
Git工作流
- 配置.gitignore
- 添加commitlint和husky(如需)
项目启动后的优化方向
-
性能优化
- 代码分割
- 按需加载
- 图片压缩
-
安全优化
- XSS防护
- CSRF防护
- API安全策略
-
SEO优化(如需)
- SSR或静态生成考虑
- 元信息管理
-
监控与错误追踪
- 错误边界处理
- 接入Sentry等监控工具
23. 你知道vue的模板语法用的是哪个web模板引擎的吗?说说你对它的理解
1. 本质
Vue 没有使用第三方模板引擎 (如 Mustache/EJS),而是自研了一套基于 HTML 的编译型模板语法 ,最终编译成虚拟 DOM 渲染函数 ,Vue 模板是专为响应式组件设计的编译型语法,比传统模板引擎更强大,比 JSX 更易读。。
2. 核心特点
- 声明式绑定 :
{{ data }}
自动响应数据变化。 - 指令系统 :
v-if
、v-for
等控制 DOM,比传统引擎(如 Handlebars 的{{#if}}
)更直观。 - 编译优化:模板会被预编译为 JS 代码,运行时直接操作虚拟 DOM,性能远超传统字符串拼接引擎。
指令系统:
v-if
:条件渲染v-for
:列表渲染v-bind
:动态属性v-on
:事件监听
3. 与传统引擎对比
Vue 模板 | Mustache/Handlebars | |
---|---|---|
更新机制 | 响应式(自动更新) | 手动重新渲染 |
性能 | 虚拟 DOM 差量更新 | 全量替换 HTML |
扩展能力 | 组件化 + 自定义指令 | 依赖 Helper 函数 |
4. 编译过程
html
<div>{{ message }}</div>
↓ 编译为 ↓
javascript
// vue2
function render() { return h('div', message) }
// vue3
function render(_ctx) {
return _createVNode("div", null, _toDisplayString(_ctx.message))
}
5. 为什么不用 JSX?
- 模板更贴合 HTML:对设计师/后端开发者更友好。
- 静态优化:Vue 模板能标记静态节点,提升性能。
6. 性能优势
- 比传统字符串模板快5倍
- 虚拟DOM减少直接DOM操作
24. v-model原理剖析
v-model
是 Vue 中用于实现双向数据绑定 的语法糖,它本质上是一个属性绑定 + 事件监听的组合。
1. 基本实现原理
v-model
在底层会被编译为:
- 一个
value
或modelValue
的属性绑定(props) - 一个对应的事件监听(通常是
input
或update:modelValue
)
对于原生表单元素
javascript
// 原生表单元素
<input v-model="text">
// 被编译为
<input
:value="text"
@input="text = $event.target.value"
>
2. 在自定义组件上的实现
在 Vue 3 中,自定义组件的 v-model
默认使用 modelValue
prop 和 update:modelValue
事件
html
<CustomComponent v-model="message" />
↓ 编译为 ↓
<CustomComponent
:modelValue="message"
@update:modelValue="newValue => message = newValue"
/>
Vue3升级 多v-model支持:
html
<CustomComponent
v-model:name="userName"
v-model:email="userEmail"
/>
↓ 编译为 ↓
<CustomComponent
:name="userName"
@update:name="newValue => userName = newValue"
:email="userEmail"
@update:email="newValue => userEmail = newValue"
/>
3. 在不同元素上的实现差异
文本输入框(input type="text")
- 绑定
value
属性 - 监听
input
事件
复选框(checkbox)
- 绑定
checked
属性 - 监听
change
事件 - 处理数组值的情况
单选按钮(radio)
- 绑定
checked
属性 - 监听
change
事件
选择框(select)
- 绑定
value
属性 - 监听
change
事件
25. 多语言项目实践
完整解决方案:
javascript
// i18n.js
import { createI18n } from 'vue-i18n'
const messages = {
en: {
greeting: 'Hello {name}',
date: 'Today is {date}'
},
zh: {
greeting: '你好 {name}',
date: '今天是 {date}'
}
}
const i18n = createI18n({
locale: localStorage.getItem('lang') || 'zh',
messages,
pluralizationRules: {
zh(choice) {
// 中文复数规则
}
}
})
// 组件中使用
const { t } = useI18n()
console.log(t('greeting', { name: 'John' }))
26. 计算属性命名冲突
核心规则:
- ❌ 禁止与data同名
- 原因:两者都挂载到组件实例
- 解决方案:
javascript
data() {
return { rawCount: 0 } // data用raw前缀
},
computed: {
formattedCount() { // computed用formatted前缀
return this.rawCount * 2
}
}
27. data与methods同名问题
重要结论:
- ❌ 严格禁止同名
- 优先级:methods > data
- 后果:方法会覆盖数据属性
- 解决方案:
javascript
data() {
return { userCount: 0 } // 名词命名
},
methods: {
countUsers() { } // 动词命名
}
28.怎么给vue定义全局的方法?
1. 通过 Vue.prototype (Vue 2)
javascript
// main.js
Vue.prototype.$myMethod = function() {
console.log('这是全局方法')
}
// 组件中使用
this.$myMethod()
2. 使用全局混入 (Vue 2/3 都适用)
javascript
// main.js
Vue.mixin({
methods: {
$globalMethod() {
console.log('全局混入方法')
}
}
})
// 组件中使用
this.$globalMethod()
3. 使用 provide/inject (Vue 2.2+/3 推荐)
javascript
// 根组件
export default {
provide() {
return {
app: {
globalMethod: () => console.log('注入的全局方法')
}
}
}
}
// 子组件中使用
export default {
inject: ['app'],
mounted() {
this.app.globalMethod()
}
}
4. 使用 Composition API 的 provide (Vue 3)
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.provide('globalMethod', () => {
console.log('Composition API 全局方法')
})
// 组件中使用
import { inject } from 'vue'
export default {
setup() {
const globalMethod = inject('globalMethod')
globalMethod()
}
}
5. 使用插件方式 (最规范的做法)
javascript
// plugins/globalMethods.js
export default {
install(app) {
app.config.globalProperties.$formatDate = (date) => {
return new Date(date).toLocaleDateString()
}
}
}
// main.js
import globalMethods from './plugins/globalMethods'
app.use(globalMethods)
// 组件中使用
this.$formatDate(new Date())
注意事项
- 避免过度使用全局方法,优先考虑组件间通信或 Vuex/Pinia 状态管理
- 全局方法命名建议添加前缀(如
$
)以避免命名冲突 - 对于工具类函数,也可以考虑单独模块导出而不是挂载到 Vue 实例
29. vue2.0不再支持v-html中使用过滤器了怎么办?
为什么 Vue 移除了这个功能?
Vue 团队认为:
- 过滤器在
v-html
中的使用场景有限 - 增加了模板编译的复杂性
- 可能导致安全问题不易被发现
- 使用方法和计算属性是更明确的替代方案
在 Vue 2.0 中,确实不再支持在 v-html
指令中直接使用过滤器。以下是几种替代方案:
1. 使用方法替代过滤器
javascript
// 组件中定义方法
methods: {
formatHtml(html) {
// 在这里实现你的过滤逻辑
return html.replace(/\n/g, '<br>')
}
}
html
<!-- 模板中使用 -->
<div v-html="formatHtml(rawHtml)"></div>
2. 使用计算属性
javascript
computed: {
processedHtml() {
// 在这里实现过滤逻辑
return this.someFilter(this.rawHtml)
},
someFilter() {
// 定义过滤器逻辑
return function(value) {
return value.toUpperCase() // 示例
}
}
}
html
<div v-html="processedHtml"></div>
3. 创建自定义指令
javascript
// 全局注册指令
Vue.directive('format-html', {
bind: function(el, binding) {
// 在这里实现过滤逻辑
el.innerHTML = binding.value.replace(/\n/g, '<br>')
}
})
html
<!-- 使用指令 -->
<div v-format-html="rawHtml"></div>
4. 使用组件封装
javascript
Vue.component('safe-html', {
props: ['html'],
methods: {
filter(html) {
// 过滤逻辑
return html
}
},
template: '<div v-html="filter(html)"></div>'
})
html
<safe-html :html="rawHtml"></safe-html>
5. 使用第三方库
可以考虑使用专门处理 HTML 的库,如:
DOMPurify
(安全过滤)marked
(Markdown 转换)
javascript
import DOMPurify from 'dompurify'
// 在方法中使用
methods: {
sanitize(html) {
return DOMPurify.sanitize(html)
}
}
最佳实践建议
- 安全性优先 :使用
v-html
时务必注意 XSS 风险,建议使用DOMPurify
等库进行净化 - 性能考虑:对于频繁更新的内容,使用计算属性比方法更高效
- 代码组织:复杂的过滤逻辑可以提取到单独的模块/工具函数中
30.怎么解决vue打包后静态资源图片失效的问题?

常见原因分析
- 路径错误:开发和生产环境路径不一致
- 文件未正确打包:图片未被正确处理或复制到 dist 目录
- 相对路径问题:部署到子目录时路径解析错误
- URL 处理方式不当:动态绑定的图片路径未正确处理
解决方案:
1. 正确引用静态资源
javascript
// 推荐使用相对路径或 require引入图片
<img :src="imgUrl" >
imgUrl: require('@/assets/images/logo.png')
在 CSS 中引用
css
/* 使用相对路径 */
background: url('./assets/images/bg.jpg');
2. 配置 vue.config.js
如果使用了自定义的 Webpack 配置,确保 url-loader
或 file-loader
正确处理图片资源
javascript
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.tap(options => ({
...options,
limit: 4096, // 小于4kb的图片转为base64
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}))
}
}
3. 使用环境变量控制路径
javascript
// .env.production
VUE_APP_PUBLIC_PATH=/my-app/
// vue.config.js
module.exports = {
publicPath: process.env.VUE_APP_PUBLIC_PATH || '/'
}
检查与调试技巧
-
检查打包后的 dist 目录:
- 确认图片文件是否被正确复制
- 检查文件命名是否符合预期(是否有 hash 值)
-
浏览器开发者工具:
- 查看图片请求的完整 URL
- 检查 404 错误的具体路径
-
查看生成的 HTML/CSS:
- 确认图片路径在生成的代码中是否正确
31.怎么解决vue动态设置img的src不生效的问题?
核心原因 :Vue 动态绑定图片路径时,直接使用字符串路径会导致 webpack 无法处理资源依赖关系,从而在打包后路径失效。
解决方案
1. 使用 require 引入图片(推荐)
javascript
// 方法1:直接require
<img :src="require('@/assets/images/' + imgName)" />
const imageUrl = require(`@/assets/${dynamicName}.png`);
// 方法2:在data/computed中处理
data() {
return {
dynamicImg: require('@/assets/images/example.jpg')
}
}
// 3.提前导入所有可能的图片
const imageMap = {
'cat': require('@/assets/cat.jpg'),
'dog': require('@/assets/dog.jpg')
}
// 4.使用new URL() 引入图片(适用于 Vite 项目)
const imageUrl = new URL(`../assets/${dynamicName}.png`, import.meta.url).href;
2. 使用 import 引入图片
javascript
// 在script部分先import
import imgUrl from '@/assets/images/dynamic-img.jpg'
export default {
data() {
return {
dynamicSrc: imgUrl
}
}
}
3. 处理动态路径拼接
javascript
// 工具函数
methods: {
getImgUrl(img) {
return require(`@/assets/images/${img}`)
}
}
// 使用
<img :src="getImgUrl('product-' + id + '.jpg')" />
4. 使用 public 目录(不推荐)
javascript
// 将图片放在public/img目录
<img :src="'/img/' + imgName" />
// 注意:public目录文件不会被webpack处理
常见问题排查
-
路径大小写问题
确保路径中的大小写与实际文件完全一致
-
路径拼接错误
javascript// 错误示例(缺少文件后缀) require(`@/assets/images/${imgName}`) // 如果imgName不带.jpg会失败
-
webpack配置问题 确保 vue.config.js 正确配置了文件加载器:
javascriptmodule.exports = { chainWebpack: config => { config.module .rule('images') .use('url-loader') .loader('url-loader') .tap(options => ({ limit: 8192, name: 'img/[name].[hash:8].[ext]' })) } }
最佳实践
- 小图片:使用 require 动态加载
- 大量动态图片:考虑放在 public 目录或使用 CDN
- 生产环境:确保路径是相对路径(配置 publicPath: './')
32. 使用vue后怎么针对搜索引擎做SEO优化?
专业解决方案:
- 服务端渲染(SSR)
- 使用Nuxt.js框架
- 配置
nuxt.config.js
中的target: 'server'
- 优点:完美解决SPA的SEO问题
- 缺点:需要Node.js服务器支持
- 预渲染(Prerender)
bash
npm install prerender-spa-plugin
javascript
// vue.config.js
new PrerenderSPAPlugin({
routes: ['/', '/about'],
renderer: new PrerenderSPAPlugin.PuppeteerRenderer()
})
- 适用:静态内容为主的网站
- 动态Meta管理
javascript
// 使用vue-meta插件
head() {
return {
title: this.pageTitle,
meta: [
{ hid: 'description', name: 'description', content: this.pageDesc }
]
}
}
33. 跟keep-alive有关的生命周期
专业解析:
- activated
- 触发时机:被缓存的组件重新激活时
- 典型应用:
javascript
activated() {
this.fetchData() // 重新获取数据
this.startTimer() // 恢复定时器
}
- deactivated
- 触发时机:组件被缓存时
- 典型应用:
javascript
deactivated() {
clearInterval(this.timer) // 清除定时器
this.cancelRequest() // 取消未完成请求
}
34. 框架选型(Vue/React/AngularJS)
专业对比分析:
维度 | Vue | React | AngularJS |
---|---|---|---|
学习曲线 | 平缓 | 中等 | 陡峭 |
数据绑定 | 双向 | 单向 | 双向 |
模板语法 | HTML-based | JSX | 指令系统 |
状态管理 | Vuex/Pinia | Redux/MobX | Service |
适用场景 | 快速开发 | 大型应用 | 企业级应用 |
选择Vue的核心理由:
- 渐进式框架,灵活易上手
- 单文件组件开发体验优秀
- 中文文档和社区资源丰富
- 性能优化良好,体积小巧
35. Vue2.0的IE兼容性
专业兼容方案:
- 支持范围
- 官方支持:IE9+
- 实际测试:IE10+更稳定
- 必备polyfill
javascript
// babel.config.js
module.exports = {
presets: [
['@vue/cli-plugin-babel/preset', {
useBuiltIns: 'entry',
corejs: 3
}]
]
}
- IE专属问题解决
- 避免使用ES6+语法
- CSS变量添加兼容前缀
- 第三方库检查IE支持性
36. Todo应用开发思路
专业实现方案:
- 架构设计
- 状态管理
javascript
// store/todos.js
state: () => ({
todos: [
{
id: 1,
text: 'Learn Vue',
completed: false,
createdAt: '2023-01-01'
}
],
filter: 'all' // all|active|completed
})
- 核心功能实现
javascript
// 添加待办
addTodo(text) {
this.todos.push({
id: Date.now(),
text,
completed: false
})
}
// 过滤显示
filteredTodos() {
switch(this.filter) {
case 'active': return this.todos.filter(t => !t.completed)
case 'completed': return this.todos.filter(t => t.completed)
default: return this.todos
}
}
37. Vue风格指南
关键规范要点:
- 组件命名
- 始终使用多单词(避免与HTML元素冲突)
- 基础组件加
Base
前缀
- Prop定义
javascript
props: {
status: {
type: String,
required: true,
validator: v => ['success', 'pending'].includes(v)
}
}
- 模板规范
- 为v-for设置key
- 避免v-if和v-for一起使用
- 组件样式使用scoped
- 代码组织
-
单文件组件顺序:
html<template>...</template> <script>...</script> <style scoped>...</style>
38. Vue版本演进
1.x vs 2.x专业对比:
特性 | Vue 1.x | Vue 2.x |
---|---|---|
架构 | 无虚拟DOM | 引入虚拟DOM |
性能 | 较慢 | 快2-4倍 |
响应式 | 基于getter/setter | 使用defineProperty |
指令系统 | 复杂 | 简化 |
组件通信 | <math xmlns="http://www.w3.org/1998/Math/MathML"> d i s p a t c h / dispatch/ </math>dispatch/broadcast | 事件总线 |
生命周期 | 较多钩子 | 精简优化 |
39. key的作用原理
专业深度解析:
- diff算法核心
- 无key:就地复用节点,可能产生错误状态
- 有key:精准识别节点,正确更新DOM
- 虚拟DOM对比过程
- 生产环境实践
html
<!-- 正确用法 -->
<div v-for="item in items" :key="item.id">
<!-- 错误用法 -->
<div v-for="(item, index) in items" :key="index">
40. 重置data的方法
专业实现方案:
- 基础重置
javascript
Object.assign(this.$data, this.$options.data.call(this))
- 深度重置
javascript
function deepReset() {
const initial = this.$options.data()
Object.keys(initial).forEach(key => {
if (Array.isArray(initial[key])) {
this[key] = []
} else if (typeof initial[key] === 'object') {
this[key] = JSON.parse(JSON.stringify(initial[key]))
} else {
this[key] = initial[key]
}
})
}
- 注意事项
- 不会触发响应式更新
- 引用类型需要特殊处理
- 避免直接修改$data引用
41. 保留HTML注释
专业配置方法:
- 全局配置
javascript
new Vue({
comments: true, // 默认false
template: `
<!-- 开发调试注释 -->
<div>主要内容</div>
`
})
- 编译后结果
html
<!-- 开发调试注释 -->
<div>主要内容</div>
- 生产环境建议
- 通过构建工具移除注释
- 减少代码体积
42. Vue.observable
专业应用场景:
- 小型状态管理
javascript
// store.js
export const state = Vue.observable({
count: 0,
increment() {
this.count++
}
})
- 响应式原理
javascript
// 近似实现
function observable(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key)
})
return obj
}
- 与Vuex对比 | 特性 | Vue.observable | Vuex | |------------|---------------------|--------------------| | 复杂度 | 简单 | 复杂 | | 功能 | 基础响应式 | 完整状态管理 | | 调试 | 无工具支持 | Devtools集成 |
43. scoped样式原理
专业实现机制:
- 编译转换过程
html
<!-- 源码 -->
<style scoped>
.button { color: red; }
</style>
<!-- 编译后 -->
<style>
.button[data-v-f3f3eg9] { color: red; }
</style>
- 深度作用选择器
css
/* Vue2语法 */
/deep/ .child-component { ... }
/* Vue3语法 */
::v-deep(.child-component) { ... }
- 性能影响
- 增加CSS特异性
- 轻微影响选择器匹配速度
- 组件样式隔离性好
44. Vue3.0改进期待
已实现重要特性:
- Composition API
javascript
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
return { count, double }
}
- 性能优化
- 打包体积减少41%
- 渲染速度提升55%
- 内存占用减少54%
- TypeScript支持
- 更好的类型推断
- 更完善的类型定义
45. Vue边界情况
专业处理方案:
- 访问根实例
javascript
this.$root
- 强制更新
javascript
this.$forceUpdate()
- 递归组件
javascript
name: 'TreeNode',
components: { TreeNode }
- 循环引用
javascript
components: {
ComponentA: () => import('./ComponentA')
}
- 程序化事件
javascript
this.$on('custom-event', handler)
this.$once('custom-event', handler)
this.$off('custom-event', handler)
46. 如何在子组件中访问父组件的实例?
专业解决方案:
- 使用
$parent
(不推荐)
javascript
// 子组件中
this.$parent.parentMethod()
- Props传递父组件引用(推荐)
html
<!-- 父组件 -->
<child :parent="this"></child>
<!-- 子组件 -->
<script>
export default {
props: ['parent'],
methods: {
callParent() {
this.parent.parentMethod()
}
}
}
</script>
- Provide/Inject(推荐用于深层嵌套)
javascript
// 父组件
export default {
provide() {
return {
parent: this
}
}
}
// 子组件
export default {
inject: ['parent'],
methods: {
callParent() {
this.parent.parentMethod()
}
}
}
47. watch的属性用箭头函数定义结果会怎么样?
专业分析:
- 问题现象:
javascript
watch: {
value: () => { // 错误用法
console.log(this) // undefined
}
}
- 根本原因:
- 箭头函数绑定父级上下文
- Vue无法将组件实例绑定到
this
- 正确写法:
javascript
watch: {
value: function(newVal, oldVal) { // 标准函数
console.log(this) // 组件实例
}
// 或
value(newVal, oldVal) { // 方法简写
console.log(this)
}
}
48. methods的方法用箭头函数定义结果会怎么样?
专业分析:
- 问题代码:
javascript
methods: {
handleClick: () => { // 错误用法
console.log(this) // 不是组件实例
}
}
- 影响范围:
- 无法访问
this
上的数据/方法 - 事件绑定失效
- 生命周期钩子调用异常
- 解决方案:
javascript
methods: {
// 标准函数定义
handleClick() {
console.log(this) // 组件实例
}
}
49. 在vue项目中如何配置favicon?
专业配置方案:
- 基础配置:
html
<!-- public/index.html -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
- 动态favicon:
javascript
// 组件中修改
const changeFavicon = (iconUrl) => {
const link = document.querySelector("link[rel*='icon']") ||
document.createElement('link')
link.type = 'image/x-icon'
link.rel = 'shortcut icon'
link.href = iconUrl
document.head.appendChild(link)
}
- 多尺寸favicon:
html
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
50. babel-polyfill的作用
专业解析:
- 核心功能:
- 提供ES6+环境的polyfill
- 实现Promise、Set、Map等新特性
- 支持Array.includes等新方法
- 现代替代方案:
javascript
// babel.config.js
module.exports = {
presets: [
['@vue/cli-plugin-babel/preset', {
useBuiltIns: 'usage', // 按需引入
corejs: 3 // 指定core-js版本
}]
]
}
- 兼容性处理范围:
- IE9+兼容
- 旧版浏览器API补全
- 异步语法转换
51. Vue的错误处理机制
专业解决方案:
- 全局错误处理:
javascript
Vue.config.errorHandler = (err, vm, info) => {
console.error(`Error: ${err.toString()}\nInfo: ${info}`)
// 上报错误到监控系统
}
- 组件级错误捕获:
javascript
errorCaptured(err, vm, info) {
// 阻止错误继续向上传播
return false
}
- 异步错误处理:
javascript
window.onerror = function(message, source, lineno, colno, error) {
// 处理全局未捕获异常
}
52. e.target vs e.currentTarget
专业区别:
特性 | e.target | e.currentTarget |
---|---|---|
触发元素 | 实际触发事件的元素 | 绑定事件的元素 |
事件委托 | 指向子元素 | 始终指向绑定元素 |
典型应用场景 | 识别具体点击的子项 | 保证始终访问事件绑定元素 |
示例:
html
<div @click="handleClick">
<button>按钮</button>
</div>
<script>
methods: {
handleClick(e) {
console.log(e.target) // <button>
console.log(e.currentTarget) // <div>
}
}
</script>
53. .vue文件中style和script的必要性
专业分析:
- style标签:
- 非必须,可以外部引入CSS
- 推荐使用scoped避免样式污染
- 支持多种预处理器(Sass/Less)
- script标签:
- 非必须,纯展示组件可不写
- 但实际开发中95%组件需要script
- 用于处理逻辑、数据、方法等
最小组件示例:
html
<template>
<div>Hello World</div>
</template>
<!-- 无script和style -->
54. 强制刷新组件方案
专业方法:
- 暴力刷新:
javascript
this.$forceUpdate()
- key-changing(推荐):
html
<component :key="componentKey" />
<script>
methods: {
forceRerender() {
this.componentKey += 1; // 修改key值触发重新渲染
}
}
</script>
- v-if控制:
html
<component v-if="show" />
<script>
methods: {
reload() {
this.show = false
this.$nextTick(() => this.show = true)
}
}
</script>
55. 父组件接收子组件多个参数
专业方案:
- 对象形式传递:
javascript
// 子组件
this.$emit('submit', { name, age, email })
// 父组件
<child @submit="handleSubmit" />
methods: {
handleSubmit({ name, age, email }) {
// 解构参数
}
}
- 参数列表形式:
javascript
// 子组件
this.$emit('submit', name, age, email)
// 父组件
<child @submit="handleSubmit(arguments)" />
methods: {
handleSubmit(args) {
const [name, age, email] = args
}
}
56. Vue最佳实践总结
专业建议:
- 组件设计:
- 单一职责原则
- 合理划分容器组件和展示组件
- 使用name属性便于调试
- 状态管理:
- 避免直接修改props
- 复杂状态使用Pinia/Vuex
- 遵循单向数据流
- 性能优化:
- 合理使用v-if和v-show
- 列表渲染务必设置key
- 组件懒加载
- 代码风格:
- 组件名使用PascalCase
- 方法名使用camelCase
- 自定义事件使用kebab-case
57. 自定义事件无效解决方案
专业排查:
- 事件名大小写:
javascript
// 子组件
this.$emit('myEvent') // 错误
this.$emit('my-event') // 正确
- 原生事件监听:
html
<child @click.native="handleClick" />
- 事件验证:
javascript
emits: {
// 验证自定义事件
'my-event': (payload) => {
return typeof payload === 'string'
}
}
58. 属性与方法同名问题
专业分析:
- 冲突现象:
javascript
data() {
return { count: 0 }
},
methods: {
count() { return 1 } // 会覆盖data中的count
}
- 底层原理:
- methods会覆盖data中的同名属性
- Vue会在控制台输出警告
- 解决方案:
- 严格遵循命名规范
- data使用名词,methods使用动词
59. _/$开头变量处理
专业解析:
- 特殊处理规则:
- Vue不会代理
_
或$
开头的属性 - 避免与Vue内部API冲突
- 访问方式:
javascript
data() {
return { _private: 'secret' }
},
methods: {
getPrivate() {
return this.$data._private // 必须通过$data访问
}
}
60. v-for遍历对象顺序
专业解答:
- 默认顺序:
- 遵循
Object.keys()
顺序 - 不保证插入顺序(尤其在不同JavaScript引擎中)
- 保证顺序的方法:
javascript
computed: {
sortedItems() {
return Object.entries(this.obj)
.sort((a, b) => a[0].localeCompare(b[0]))
.reduce((acc, [key, val]) => {
acc[key] = val
return acc
}, {})
}
}
61. 组件扩展方案
专业方法:
- 组合式继承:
javascript
// base-component.js
export default {
data() {
return { baseData: 'value' }
},
methods: {
baseMethod() { /*...*/ }
}
}
// extended-component.vue
import BaseComponent from './base-component'
export default {
extends: BaseComponent,
// 添加新功能
}
- 高阶组件:
javascript
function withLogging(WrappedComponent) {
return {
mounted() {
console.log('Component mounted')
},
render(h) {
return h(WrappedComponent, {
props: this.$props
})
}
}
}
- 插槽扩展:
html
<!-- 基础组件 -->
<template>
<div>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
<!-- 扩展使用 -->
<base-component>
<template #header>自定义头部</template>
自定义内容
</base-component>
62. <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s 和 attrs和 </math>attrs和listeners使用场景
专业解析:
- $attrs:
- 包含父作用域中不作为props识别的attribute
- 典型场景:高阶组件封装
html
<base-input v-bind="$attrs"></base-input>
- $listeners:
- 包含父作用域中的v-on事件监听器
- 典型场景:事件透传
html
<base-button v-on="$listeners"></base-button>
- 组合使用:
html
<advanced-component
v-bind="$attrs"
v-on="$listeners"
></advanced-component>
63. 部署后404问题分析
专业排查方案:
- 路由模式问题:
javascript
// history模式需要服务器配置
const router = new VueRouter({
mode: 'history',
routes: [...]
})
- Nginx配置示例:
nginx
location / {
try_files $uri $uri/ /index.html;
}
- 静态资源路径问题:
javascript
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/project-name/'
: '/'
}
64. v-once使用场景
专业应用场景:
- 静态内容优化:
html
<div v-once>{{ staticContent }}</div>
- 性能敏感区域:
html
<template v-for="item in list">
<div v-once :key="item.id">{{ item.name }}</div>
</template>
- 避免不必要的更新:
html
<footer v-once>© 2023 Company</footer>
65. .lazy修饰符原理
专业解析:
- 默认行为对比:
html
<!-- 默认行为 -->
<input v-model="text"> <!-- 实时更新 -->
<!-- lazy修饰符 -->
<input v-model.lazy="text"> <!-- change事件触发更新 -->
- 底层实现:
javascript
// 编译后代码
h('input', {
on: {
change: $event => (text = $event.target.value)
}
})
66. 单根元素限制原因
专业解释:
- 虚拟DOM差异算法要求:
- 需要单一根节点进行对比
- Vue3 Fragment支持多根节点
- 代码示例对比:
html
<!-- Vue2 错误 -->
<template>
<div>A</div>
<div>B</div>
</template>
<!-- Vue3 支持 -->
<template>
<div>A</div>
<div>B</div>
</template>
67. EventBus重复触发解决方案
专业方案:
- 手动注销事件:
javascript
// 组件内
beforeDestroy() {
EventBus.$off('eventName', this.eventHandler)
}
- 使用once监听:
javascript
EventBus.$once('eventName', callback)
- 路由守卫处理:
javascript
router.beforeEach((to, from, next) => {
EventBus.$off()
next()
})
68. 修改打包文件路径
专业配置:
- 基础配置:
javascript
// vue.config.js
module.exports = {
outputDir: 'dist',
assetsDir: 'static',
filenameHashing: true
}
- CDN路径:
javascript
module.exports = {
publicPath: 'https://cdn.example.com/project/'
}
69. Vue与原生App交互
专业方案:
- JS Bridge通信:
javascript
// Vue调用原生
window.WebViewJavascriptBridge.callHandler('nativeMethod', data)
// 监听原生调用
window.WebViewJavascriptBridge.registerHandler('jsMethod', callback)
- URL Scheme:
javascript
location.href = 'myapp://method?param=value'
- PostMessage:
javascript
window.postMessage(JSON.stringify(message), '*')
70. Tab切换实现
专业代码:
html
<template>
<div class="tabs">
<div
v-for="(tab, index) in tabs"
:key="index"
@click="currentTab = index"
:class="{ active: currentTab === index }"
>
{{ tab.title }}
</div>
</div>
<component :is="tabs[currentTab].component" />
</template>
<script>
export default {
data() {
return {
currentTab: 0,
tabs: [
{ title: 'Tab1', component: 'Tab1Content' },
{ title: 'Tab2', component: 'Tab2Content' }
]
}
}
}
</script>
71. 递归组件
专业示例:
vue
<template>
<div>
{{ node.name }}
<tree-node
v-for="child in node.children"
:key="child.id"
:node="child"
/>
</div>
</template>
<script>
export default {
name: 'TreeNode',
props: ['node']
}
</script>
72. 访问子组件实例
专业方案:
- ref方式:
html
<child-component ref="child"></child-component>
<script>
this.$refs.child.method()
</script>
- $children(不推荐):
javascript
this.$children[0].method()
73. 访问父组件实例
专业方案:
- $parent(不推荐):
javascript
this.$parent.parentMethod()
- Provide/Inject(推荐):
javascript
// 父组件
provide() {
return {
parent: this
}
}
// 子组件
inject: ['parent']
74. 访问根实例
专业方案:
javascript
this.$root.rootMethod()
75. Object.defineProperty理解
专业解析:
- Vue2响应式核心:
javascript
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
track()
return value
},
set(newVal) {
// 触发更新
trigger()
value = newVal
}
})
- 局限性:
- 无法检测数组索引变化
- 无法检测对象属性添加/删除
76. 原生事件销毁
专业建议:
- 必须手动销毁:
javascript
mounted() {
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
}
- 原因:
- Vue无法管理原生事件监听
- 可能导致内存泄漏
77. 定时器销毁
专业方案:
javascript
data() {
return {
timer: null
}
},
mounted() {
this.timer = setInterval(() => {
// do something
}, 1000)
},
beforeDestroy() {
clearInterval(this.timer)
}
78. 组件销毁时机
专业总结:
- 路由切换
- v-if条件为false
- 父组件销毁
- 调用$destroy()方法
- keep-alive排除
79. 大数据渲染优化
专业方案:
- 虚拟滚动:
bash
npm install vue-virtual-scroller
- 分页加载:
javascript
// 只渲染可视区域数据
displayData() {
return this.bigData.slice(
(this.currentPage - 1) * this.pageSize,
this.currentPage * this.pageSize
)
}
- 冻结数据:
javascript
this.bigData = Object.freeze(bigData)
80. this使用注意事项
专业建议:
- 避免箭头函数:
javascript
// 错误
methods: {
handleClick: () => {
console.log(this) // undefined
}
}
// 正确
methods: {
handleClick() {
console.log(this) // 组件实例
}
}
- 异步回调:
javascript
// 保存this引用
const vm = this
setTimeout(function() {
vm.data = 'value'
}, 100)
81. JSX使用理解
专业解析:
- 配置支持:
bash
npm install @vue/babel-preset-jsx
- 示例对比:
javascript
// 模板语法
<template>
<div>{{ message }}</div>
</template>
// JSX语法
render() {
return <div>{this.message}</div>
}
- 优势场景:
- 动态生成复杂DOM结构
- 更好的TypeScript支持
82. 组件命名规范
专业建议:
- 文件命名:
- MyComponent.vue (PascalCase)
- my-component.vue (kebab-case)
- 组件注册:
javascript
components: {
MyComponent, // 推荐
'my-component': MyComponent // 兼容
}
- 目录结构:
arduino
components/
|- BaseButton.vue // 基础组件
|- AppHeader.vue // 业务组件
|- TheNavbar.vue // 唯一组件
83. Vue2+TypeScript配置
专业配置:
- 安装依赖:
bash
npm install @vue/cli-plugin-typescript --save-dev
- tsconfig.json:
json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node"
}
}
- shims-vue.d.ts:
typescript
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
84.
<template>
标签的作用
专业解析:
- 模板容器功能:
- 包裹多个元素而不渲染额外DOM节点
- 支持
v-if
/v-for
等指令分组
html
<template v-if="show">
<div>A</div>
<div>B</div>
</template>
- 特殊场景应用:
- Vue2中解决单根元素限制
- Vue3支持多根节点后仍可用于逻辑分组
- 与普通div的区别 : | 特性 |
<template>
|<div>
| |---------------|---------------------------|---------------------------| | DOM渲染 | 不渲染 | 渲染为实际节点 | | 支持指令 | 是 | 是 | | 样式作用 | 无 | 有 |
85.
is
特性的应用场景
专业应用:
- 动态组件:
html
<component :is="currentComponent"></component>
- 解决DOM模板限制:
html
<table>
<tr is="vue-row"></tr>
</table>
- HTML元素限制规避:
html
<ul>
<li is="vue-item"></li>
</ul>
- Vue3变化:
- 仍支持但推荐使用
v-is
指令
html
<table>
<tr v-is="'vue-row'"></tr>
</table>
86.
:class
和:style
表示方式
专业分类:
- :class的三种写法:
html
<!-- 对象语法 -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
<!-- 数组语法 -->
<div :class="[activeClass, errorClass]"></div>
<!-- 三目运算 -->
<div :class="isActive ? 'active' : 'inactive'"></div>
- :style的两种写法:
html
<!-- 对象语法 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<!-- 数组语法 -->
<div :style="[baseStyles, overridingStyles]"></div>
87. 函数式组件
专业理解:
- 核心特征:
- 无状态(无data)
- 无实例(无this上下文)
- 更高性能
- 实现方式:
javascript
Vue.component('functional-button', {
functional: true,
props: ['text'],
render(h, context) {
return h('button', context.data, context.props.text)
}
})
- 适用场景:
- 纯展示型组件
- 高阶组件(HOC)
- 性能敏感区域
88. 修改模板分隔符
专业配置:
- 全局修改:
javascript
new Vue({
delimiters: ['${', '}'] // 替换默认的{{ }}
})
- 组件级修改:
javascript
export default {
delimiters: ['#[', ']'],
template: `<div>#[message]</div>`
}
- 注意事项:
- 避免与Markdown等语法冲突
- 团队统一规范
89. name选项的作用
专业用途:
- 递归组件:
javascript
name: 'TreeNode',
components: { TreeNode }
- 调试工具显示:
- Vue DevTools中显示组件名
- 错误堆栈追踪更清晰
- keep-alive缓存:
html
<keep-alive include="ComponentA">
<component :is="currentComponent"></component>
</keep-alive>
- 动态组件匹配:
javascript
components: {
[name]: componentDefinition
}
90. provide/inject机制
专业理解:
- 跨层级通信:
javascript
// 祖先组件
provide() {
return {
theme: this.theme
}
}
// 后代组件
inject: ['theme']
- 响应式处理:
javascript
provide() {
return {
getTheme: () => this.theme // 函数式保持响应
}
}
- 与props对比: | 特性 | provide/inject | props | |------------|---------------------|----------------------| | 数据流向 | 任意层级向下 | 父子组件之间 | | 响应性 | 需特殊处理 | 自动响应 | | 适用场景 | 组件库/高阶组件 | 常规组件通信 |
91. Vue DevTools使用
专业技巧:
- 核心功能:
- 组件树查看
- 状态调试
- 事件追踪
- 性能分析
- 高级用法:
javascript
// 自定义检查器
app.config.devtools = true
// 时间旅行调试
import { createStore } from 'vuex'
- 生产环境禁用:
javascript
Vue.config.devtools = process.env.NODE_ENV !== 'production'
92. Slot机制深度解析
专业应用:
- 基本类型:
html
<!-- 默认slot -->
<slot></slot>
<!-- 具名slot -->
<slot name="header"></slot>
<!-- 作用域slot -->
<slot :user="user"></slot>
- 高级用法:
html
<!-- 动态slot名 -->
<template v-slot:[dynamicSlotName]>
<!-- 缩写语法 -->
<template #header>
- 设计模式应用:
- 布局组件(Layout)
- 复合组件(Compound)
- 渲染委托(Render Delegation)
93. 动态组件实践
专业方案:
- 基础用法:
html
<component :is="currentTabComponent"></component>
- 状态保持:
html
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
- 高级场景:
javascript
// 异步组件
const AsyncComponent = () => ({
component: import('./Component.vue'),
loading: LoadingComponent,
error: ErrorComponent
})
94. prop验证类型
完整类型列表:
- 原生构造函数:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- 自定义构造函数:
javascript
propA: {
type: Person, // 自定义类
validator: value => value instanceof Person
}
- 多类型支持:
javascript
propB: [String, Number]
95. prop验证与默认值
专业配置:
javascript
props: {
// 完整验证
propA: {
type: String,
required: true,
default: 'default value',
validator: value => value.length > 0
},
// 对象/数组默认值需工厂函数
propB: {
type: Object,
default: () => ({})
}
}
96. 路由组件缓存与更新
专业方案:
- 基础缓存:
html
<keep-alive>
<router-view></router-view>
</keep-alive>
- 条件缓存:
html
<keep-alive :include="['Home', 'User']">
<router-view></router-view>
</keep-alive>
- 缓存更新策略:
javascript
// 方案1:监听路由变化
watch: {
'$route'() {
this.refreshData()
}
},
// 方案2:使用activated钩子
activated() {
this.loadData()
}
97. 组件设计原则
专业准则:
- 单一职责原则
- 每个组件只做一件事
- 控制组件代码行数(建议<500行)
- 开放封闭原则
- 对扩展开放(通过props/slots)
- 对修改封闭(内部状态私有)
- DRY原则
- 提取可复用逻辑(mixins/composables)
- 避免重复代码
- 无副作用原则
- 纯函数式渲染
- 避免直接操作DOM
98. 组件通信设计
专业模式:
场景 | 推荐方案 | 示例 |
---|---|---|
父子通信 | props/events | <child @update="handle"> |
跨层级 | provide/inject | provide('key', value) |
全局状态 | Pinia/Vuex | store.user |
组件实例访问 | ref/template refs | this.$refs.child |
事件广播 | Mitt/EventBus | emitter.emit('event') |
99. 性能优化策略
专业建议:
- 代码层面:
- v-if和v-for避免共用
- 合理使用v-once
- 组件懒加载
- 架构层面:
- 路由懒加载
- 异步组件
- 服务端渲染
- 工具层面:
- 使用生产环境构建
- 开启Gzip压缩
- 配置CDN加速
100. TypeScript集成
专业配置:
- 完整配置链:
bash
npm install @vue/cli-plugin-typescript vue-tsc --save-dev
- tsconfig.json关键项:
json
{
"compilerOptions": {
"strict": true,
"jsx": "preserve",
"types": ["webpack-env"]
}
}
- 组件写法:
typescript
import { defineComponent } from 'vue'
export default defineComponent({
props: {
message: {
type: String as PropType<string>,
required: true
}
},
setup(props) {
const count = ref(0)
return { count }
}
})