Vue与前端开发面试题集合
一、Vue核心基础
1. Vue响应式原理
问题: Vue 2和Vue 3的响应式原理有什么区别?
答案:
- Vue 2:使用Object.defineProperty()劫持对象属性的getter和setter,通过依赖收集和派发更新实现响应式。但无法检测对象属性的添加和删除,数组索引和length的修改。
- Vue 3:使用Proxy代理整个对象,可以监听对象属性的添加、删除,数组索引和length的变化,性能更好且更完整。
2. computed vs watch
问题: computed和watch的区别和使用场景?
答案:
- computed:计算属性,有缓存,依赖的响应式数据变化时才重新计算,适合模板渲染、多个数据联合计算的场景
- watch:侦听器,无缓存,每次数据变化都执行,适合数据变化时执行异步操作或开销较大的操作
3. Vue生命周期
问题: 详细说明Vue的生命周期钩子及应用场景?
答案:
- beforeCreate:实例初始化后,数据观测和事件配置之前
- created:实例创建完成,可访问data、methods,常用于数据初始化、API调用
- beforeMount:挂载开始前,render函数首次被调用
- mounted:实例挂载完成,可访问DOM,常用于DOM操作、第三方库初始化
- beforeUpdate:数据更新时,虚拟DOM重新渲染前
- updated:虚拟DOM重新渲染后
- beforeUnmount/beforeDestroy:实例销毁前,常用于清理定时器、解绑事件
- unmounted/destroyed:实例销毁后
4. 组件通信方式
问题: Vue组件间有哪些通信方式?
答案:
- Props / $emit:父子组件通信
- $parent / $children:访问父子组件实例(不推荐)
- ref:获取组件实例
- provide / inject:跨层级组件通信
- EventBus:事件总线(Vue 3中需自行实现)
- Vuex / Pinia:状态管理
- $attrs / $listeners:透传属性和事件
5. v-if vs v-show
问题: v-if和v-show的区别?
答案:
- v-if:条件渲染,切换时会销毁和重建DOM,有更高的切换开销,适合条件不频繁改变的场景
- v-show:条件展示,通过CSS的display属性切换,有更高的初始渲染开销,适合频繁切换的场景
二、Vue进阶
6. 虚拟DOM与Diff算法
问题: 什么是虚拟DOM?Vue的Diff算法是如何工作的?
答案:
虚拟DOM是用JavaScript对象描述真实DOM的树形结构。Diff算法采用同层比较策略:
- 只比较同一层级的节点
- 使用key优化列表对比(就地复用或移动元素)
- Vue 3的优化:静态标记、静态提升、事件缓存、Block Tree
7. keep-alive原理
问题: keep-alive的作用和实现原理?
答案:
keep-alive是Vue内置组件,用于缓存不活动的组件实例。原理:
- 使用LRU缓存策略存储组件实例
- 通过include/exclude控制缓存范围
- 缓存的组件有activated和deactivated生命周期钩子
- 不会在DOM中留下任何痕迹
8. nextTick原理
问题: $nextTick的作用和实现原理?
答案:
作用:在下次DOM更新循环结束后执行回调,用于获取更新后的DOM。
实现原理:使用微任务(Promise.then、MutationObserver)或宏任务(setImmediate、setTimeout)降级方案,将回调推入异步队列,在当前执行栈清空后执行。
9. Vue 3 Composition API
问题: Composition API相比Options API有什么优势?
答案:
- 更好的逻辑复用:通过composables函数封装和复用逻辑
- 更好的类型推导:对TypeScript更友好
- 更灵活的代码组织:相关逻辑可以组织在一起,不必分散在不同选项中
- 更小的打包体积:tree-shaking友好
10. 自定义指令
问题: 如何实现一个自定义指令?
答案:
// Vue 3
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// Vue 2
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
三、前端基础
11. 闭包
问题: 什么是闭包?有什么作用和缺点?
答案:
闭包是指有权访问另一个函数作用域中变量的函数。
作用:数据私有化、函数柯里化、模块化、回调函数。
缺点:内存占用较大,可能导致内存泄漏。
12. 原型链
问题: 解释JavaScript的原型链?
答案:
每个对象都有proto属性指向其构造函数的prototype,形成原型链。当访问对象属性时,会沿着原型链向上查找,直到找到该属性或到达链顶端(null)。
13. 事件循环
问题: 解释浏览器的事件循环机制?
答案:
- 执行同步代码(执行栈)
- 清空微任务队列(Promise.then、MutationObserver)
- 执行一个宏任务(setTimeout、setInterval、I/O)
- 渲染页面
- 重复以上步骤
14. 跨域解决方案
问题: 常见的跨域解决方案有哪些?
答案:
- CORS:服务端设置Access-Control-Allow-Origin
- JSONP:利用script标签不受同源策略限制
- 代理:开发环境配置devServer proxy,生产环境使用nginx反向代理
- postMessage:跨窗口通信
15. HTTP缓存
问题: HTTP缓存机制有哪些?
答案:
- 强缓存:Cache-Control(max-age)、Expires
- 协商缓存:Last-Modified/If-Modified-Since、ETag/If-None-Match
四、性能优化
16. Vue性能优化
问题: Vue应用有哪些性能优化方法?
答案:
- 合理使用v-if和v-show
- 使用key优化列表渲染
- 路由懒加载、组件异步加载
- 使用computed缓存计算结果
- 使用keep-alive缓存组件
- 虚拟滚动处理长列表
- 图片懒加载
- 避免不必要的组件抽象
- 使用Object.freeze()冻结不需要响应式的数据
17. 浏览器渲染优化
问题: 如何优化浏览器渲染性能?
答案:
- 减少重排(reflow)和重绘(repaint)
- 使用transform和opacity实现动画
- 使用will-change提示浏览器优化
- 避免强制同步布局
- 使用requestAnimationFrame
- 减少DOM操作,使用文档片段
- 使用CSS3硬件加速
18. 首屏加载优化
问题: 如何优化首屏加载速度?
答案:
- 代码分割(Code Splitting)
- 路由懒加载
- 压缩资源(Gzip、Brotli)
- 使用CDN
- 图片优化(WebP、懒加载、雪碧图)
- 服务端渲染(SSR)
- 预加载关键资源(preload、prefetch)
- 骨架屏提升感知性能
五、工程化
19. Webpack配置
问题: 说说Webpack的核心概念?
答案:
- Entry:入口,指示Webpack从哪个文件开始构建
- Output:输出,指示Webpack在哪里输出打包后的文件
- Loader:转换器,处理非JavaScript文件
- Plugin:插件,执行更广泛的任务(打包优化、压缩等)
- Mode:模式,development/production
20. 前端安全
问题: 常见的前端安全问题及防御措施?
答案:
- XSS(跨站脚本攻击):对用户输入进行转义,使用CSP,HttpOnly Cookie
- CSRF(跨站请求伪造):使用Token验证,SameSite Cookie
- 点击劫持:使用X-Frame-Options或CSP的frame-ancestors
- SQL注入:使用参数化查询,后端验证
- 敏感信息泄露:不在前端存储敏感信息,使用HTTPS
六、实战问题
21. 实现双向绑定
问题: 手写实现一个简单的双向绑定?
答案:
class Observer {
constructor(data) {
this.observe(data)
}
observe(data) {
if (!data || typeof data !== 'object') return
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive(obj, key, value) {
this.observe(value)
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addSub(Dep.target)
return value
},
set(newVal) {
if (newVal === value) return
value = newVal
dep.notify()
}
})
}
}
class Dep {
constructor() {
this.subs = []
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
22. 防抖与节流
问题: 实现防抖和节流函数?
答案:
// 防抖:延迟执行,多次触发只执行最后一次
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 节流:固定时间内只执行一次
function throttle(fn, delay) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}
23. 深拷贝
问题: 实现一个深拷贝函数?
答案:
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
if (typeof obj !== 'object') return obj
// 处理循环引用
if (hash.has(obj)) return hash.get(obj)
const cloneObj = new obj.constructor()
hash.set(obj, cloneObj)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}
24. Promise实现
问题: 简单实现Promise的核心功能?
答案:
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
七、场景题
25. 大文件上传
问题: 如何实现大文件上传?
答案:
- 文件切片(File.slice)
- 计算文件hash(使用Web Worker + spark-md5)
- 并发上传切片,控制并发数
- 服务端合并切片
- 断点续传(记录已上传的切片)
- 进度显示
26. 长列表优化
问题: 如何优化包含数万条数据的列表渲染?
答案:
- 虚拟滚动(只渲染可视区域的数据)
- 分页加载或无限滚动
- 使用Web Worker处理数据
- 使用Object.freeze冻结数据
- 合理使用key
- 避免不必要的组件更新
八、准备建议
- 理解原理:不要只记答案,要理解背后的原理
- 动手实践:对于代码题,最好能手写一遍
- 关注实战:结合实际项目经验回答
- 准备问题:准备一些要问面试官的问题
- 最新技术:关注Vue 3、Vite等新技术动向