十五. Vue中的性能优化有哪些?
1.响应式数据
数据层级不易过深,深层嵌套数据会递归生成getter/setter
2.使用Object.freeze( ) 方法冻结
- 机制: 冻结对象会阻止Vue添加响应式特性,减少getter/setter生成
- 场景: 静态配置项,大数据量的只读参考数据
- 注意: 冻结后修改数据将静默失败(严格模式报错)
// 正确用法 data() { const frozenData = Object.freeze({/* 大数据 */}) return { frozenData } }
3.使用数据时缓存值的结果,不频繁取值,减少getter和setter触发
// 反例(多次触发getter)
computed: {
total() {
return this.list.length + this.config.offset
}
}
// 正解(单次取值)
computed: {
total() {
const { length } = this.list
const { offset } = this.config
return length + offset
}
}
4.合理设置key属性
使用唯一ID,仅静态列表可用index 不推荐
5.合理使用v-show和v-if
v-show: 本质: 通过CSS切换显示(初始渲染成本高)
v-if 本质:条件编译(切换成本高)
需要频繁切换 选 v-show
不频繁切换 选 v-if
6.组件粒度控制
Vue更新机制: 以组件为最小更新单元
优化策略:
- 将高频变化的数据隔离到独立组件
- 表格每行作为独立组件防止整表重绘
<template>
<el-table :data="tableData">
<el-table-column label="操作">
<template #default="{ row }">
<!-- 将复杂交互的行内容提取为独立组件 -->
<CustomRowActions :row="row" />
</template>
</el-table-column>
</el-table>
</template>
7. 采用函数式组件-> 函数式组件开销低
三大特征:
- 无实例(无this)
- 无生命周期
- 仅依赖props和context
Vue2
// Vue 2 函数式组件 (通过functional选项声明)
export default {
functional: true,
props: ['type'],
render(h, context) {
const { props, listeners, data } = context
return h('button', {
class: [`btn-${props.type}`],
on: {
click: listeners.click || (() => {})
},
attrs: data.attrs
}, context.children)
}
}
Vue3
// Vue 3 函数式组件 (直接导出函数)
import { h } from 'vue'export default (props, context) => {
const { slots, attrs } = context
return h('div', {
class: 'tag',
...attrs
}, [
h('span', { class: 'tag-text' }, props.text),
slots.icon?.()
])
}
8.采用异步组件
原理:
1. 异步组件: 将组件定义为按需加载的函数
2. Webpack分包: 通过动态import( ) 语法触发代码分割
vue2.做法
// 1. 工厂函数方式 const AsyncComponent = () => ({ component: import('./MyComponent.vue'), // Webpack 自动分包 loading: LoadingComponent, // 加载中状态组件 error: ErrorComponent, // 加载失败组件 delay: 200, // 延迟显示加载状态(ms) timeout: 3000 // 超时时间 }) // 2. 注册组件 Vue.component('async-component', AsyncComponent)
1. import( )语法
Vue2 的异步组件中使用的import('./MyConponent.vue') 是WebPack特有的动态导入语法,它会自动触发代码分割
2. 分包实现原理
当Webpack 检测到动态 import( ) 时:
自动将目标组件拆分成独立chunk 文件
生成运行时加载逻辑(如__ webpack_require_.e)
在浏览器中按需请求该
vue3
import { defineAsyncComponent } from 'vue' // 1. 使用官方API定义 const AsyncComponent = defineAsyncComponent({ loader: () => import('./MyComponent.vue'), // 动态导入 loadingComponent: LoadingComponent, // 加载中组件 errorComponent: ErrorComponent, // 错误组件 delay: 200, // 延迟显示 timeout: 3000, // 超时时间 suspensible: false // 是否兼容<Suspense> }) // 2. 注册组件 app.component('AsyncComponent', AsyncComponent)
9.使用keep-alive缓存
使用include/exclude 精准控制缓存范围
使用场景
- Tab页切换保留表单状态
- 详情页返回列表页保持位置
10. 分页,虚拟滚动,时间分片等策略
1.虚拟列表 官方推荐: vue-virtual-scroller
背景:
当需要渲染一个包含大量项目的列表,如果一次性将所有DOM节点都渲染出来,会导致:
1. 过多的DOM节点占用大量内存
2. 渲染时间长,造成界面卡顿
3. 滚动操作变得卡顿
解决方案:
虚拟列表,其核心思想是: 只渲染可视区域内的元素,非可视区域的元素不渲染或部分渲染,使用空白占位
实现原理:
1. 计算可视区域的高度
2. 根据每个列表项的高度(固定高度或动态高度) 计算可视区域内应该显示的列表项
3. 根据滚动位置动态更新可视区域内的列表项
4. 用一个外层容器(具有滚动条)和一个内层容器(通过padding和transform模拟总高度)来模拟完整列表
2.时间分片
背景:
当需要处理大量计算(如渲染大量元素)时,长时间占用主线程会导致页面卡顿(无法响应用户交互,动画掉帧等)
解决方案:
时间分片,将一个大任务拆分成多个小任务,在每一帧(通过requestAnimationFrame) 或空闲时间是通过requestIdleCallback 执行一小部分,从而避免阻塞主线程
vue中实现时间分片:
1. 使用requestAnimationFrame 或requestIdleCallback
requestAnimationFrame : 专门为动画优化,在下一次浏览器重绘之前执行回调函数。
requestAnimationFrame : 在浏览器空闲时期执行低优先级任务
2. 每次处理一小部分数据,并更新视图
3. 直到所有数据处理完成
<template>
<div>
<div v-for="item in displayedItems" :key="item.id">{{ item.content }}</div>
</div>
</template><script>
export default {
data() {
return {
allItems: [], // 所有数据
displayedItems: [], // 当前已显示的数据
chunkSize: 50, // 每次渲染的数量
currentIndex: 0
};
},
mounted() {
// 假设从某个地方获取大量数据
this.allItems = /* 获取数据 */;
this.loadNextChunk();
},
methods: {
loadNextChunk() {
if (this.currentIndex >= this.allItems.length) return;
// 每次取chunkSize条数据
const nextChunk = this.allItems.slice(
this.currentIndex,
this.currentIndex + this.chunkSize
);
this.currentIndex += this.chunkSize;
// 添加到已显示数组
this.displayedItems = [...this.displayedItems, ...nextChunk];
// 如果还有数据,继续加载下一批
if (this.currentIndex < this.allItems.length) {
requestAnimationFrame(this.loadNextChunk);
}
}
}
};
</script>
十六. 单页应用首屏加载速度慢怎么解决?
1. 使用路由懒加载,异步组件,实现组件拆分,减少入口文件体积大小(优化体验骨架屏)
异步组件:
- 将组件代码拆分为独立的s文件
- 仅在需要渲染时动态加载
- 对应Webpack的代码分割
2. 资源优化策略
1.图片压缩
2. 字体子集化,仅加载字体库中的必要字符集
3. 静态资源使用CDN加速
cnd加速: 通过全球分布的边缘节点缓存静态资源,使用户从最近节点获取内容,降低网络延迟
4.静态资源缓存,采用HTTP缓存 (强制缓存,对比缓存),使用localStorage实现缓存资源
通过服务器响应头设置Cache-Control 或Expires,强制浏览器直接使用本地缓存资源
- 强制缓存 : 不发送请求,直接使用本地缓存副本.HTTP状态码:200 (from disk cache/memory cache)
控制字段 :
Cache-Control: max-age(缓存有效期的秒数)=31536000
Expires(绝对过期时间,受客户端时钟影响): Wed, 21 Oct 2025 07:28:00 GMT - 对比缓存: 必须发送请求 : 与服务器验证缓存有效性 HTTP状态码(304)
控制字段:Last-Modified: Tue, 15 Nov 2022 08:12:31 GMT
If-Modified-Since: Tue, 15 Nov 2022 08:12:31 GMTETag: "33a64df551425fcc55e4d42a148795d9"
If-None-Match: "33a64df551425fcc55e4d42a148795d9"
3.构建配置优化
1.按需引入组件库
2.启用Gzip压缩compression-webpack-plugin
生成 .gz
文件
通过压缩算法减少网络传输体积,将文本类资源(js/CSS/HTML)压缩为.gz格式
// vue.config.js
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
configureWebpack: {
plugins: [
new CompressionPlugin({
test: /\.(js|css|html)$/,
threshold: 10240 // 对超过10KB的文件压缩
})
]
}
}
需要服务器配置才能正确返回压缩文件
十七.在项目中你是如何解决跨域的?
跨域是浏览器同源策略导致的,这个是浏览器的行为(协议,主机名,端口的不同都会导致跨域问题),服务器和服务端之间进行通信是没有跨域的,跨域的实现方案有很多种,不过一般常用的就那么几种
1. CORS 由服务端设置
2. 使用构建工具代理请求
3.JSONP跨域,针对老项目或特殊场景,仅限GET请求
十八. Vue项目中有封装过axios吗?主要是封装哪方面的 ?
- 设置请求超时时间
- 根据项目环境设置请求路径
- 设置请求拦截,自动添加Token
- 设置响应拦截,对响应的状态码或者数据进行格式化
- 增添请求队列,实现loading效果
- 维护取消token,在页面切换时通过导航守卫可以取消上个页面中正在发送的请求
十九.vue要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?
1.1 常见权限控制
- 登录鉴权: 用户登录后返回Token,前端将Token保存到本地,作用用户登录的凭证,每次发送请求时会携带Token,后端会对Token进行验证,当页面刷新时我们可以已使用Token来获得用户权限
- 访问权限: 根据用户是否登录判断能否访问某个页面,通过路由守卫实现判断用户是否有权限
- 页面权限: 前端配置的路由分为两部分"通用路由配置" 和 "需要权限的路由配置",在权限路由中增加访问权限meta(备注). 用户登录后可得到对应的权限列表,通过权限列表筛查对应符合的路由信息,最后通过addRoutes方法,动态添加路由
- 按钮权限: 按钮权限一般采用自定义指令实现,当用户登录时后端会返回对应的按钮权限,在按钮上使用此指令,指令内部会判断用户是否有此按钮权限,如果没有则会移除按钮.
二十. Vue中异步组件的作用及原理
Vue中的异步组件主要用于按需加载组件,实现懒加载,从而优化首屏性能,减少打包体积,提升页面加载速度简单来说就是异步组件就是不是一开始就加载进来的组件,而是在用到时再去"动态"加载
1.异步组件的作用
- 减小首屏加载体积
避免把所有组件都打进首包,提升加载速度. - 按需加载组件
某些组件可能只在特定页面或特定条件下使用,没必要提前加载 - 结合路由实现懒加载
Vue Router中经常配合异步组件实现页面级别的按需加载
2. 使用方法 vue2+ vue3
1.vue3中使用: defineAsyncComponent
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
然后像普通组件一样用
<AsyncComp />
还可以加上加载状态或超时错误处理:
const AsyncComp = defineAsyncComponent({
loader: () => import('./components/MyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200, // 延迟显示 loading
timeout: 3000, // 超过3秒报错
})
2.vue2中使用:工厂函数(有回调和Promise写法)
1. 回调写法
Vue.component('AsyncComp', function (resolve, reject) {
import('./components/MyComponent.vue').then(resolve).catch(reject)
})
2. Promise简写
Vue.component('AsyncComp', () => import('./components/MyComponent.vue'))
3.带加载状态 / 超时 / 错误处理的高级写法
Vue.component('AsyncComp', () => ({
// 要加载的组件(必须是 Promise)
component: import('./components/MyComponent.vue'),
// 加载中显示的组件
loading: LoadingComponent,
// 加载失败时显示的组件
error: ErrorComponent,
// 展示 loading 的延迟时间(默认 200ms 才会显示 loading)
delay: 200,
// 最长等待时间,超时就报错(默认永不超时)
timeout: 3000
}))
3. 使用
export default {
components: {
'async-component': () => import('./AsyncComponent.vue')
}
}
二十一. Vue-router 有几种钩子函数,具体是什么及执行流程是怎样的
1.全局守卫
- beforeeach (to,from,next) : 路由跳转开始前触发,常用于权限校验,登录判定,必须调用next( )放行或中断
- beforeResolve(to,form,next): 所有组件内守卫和异步组件解析完毕后触发,是最后一个全局守卫,适合做异步组件加载后的最终确认
- afterEach(to,from): 路由跳转完成后触发,不接受next( ),不能阻止导航,适合做埋点,统计
2.路由独享守卫
- 在路由配置中定义的beforeEnter(to,from,next),只针对该路由生效,执行时机是在全局beforeEach之后.
3. 组件内守卫
- 在路由组件里定义
1. beforeRouteEnter(to,from,next) : 进入路由前调用,不能访问this,但可以通过next(vm=>{...})获取实例.
2. beforeRouteUpdate(to,from,next): 同一个组件复用时,路由参数变化触发
3. beforeRouteLeave(to,from,next) : 离开当前路由前触发,常用于确认提示离开
4.钩子的执行流程(假设从A跳转到B)
1. 触发所有注册的全局beforeEach
2. 触发目标路由的beforeEnter(路由独享守卫)
3. 触发当前路由组件的beforeRouteLeave(路由后置守卫)
4. 如果目标路由复用同一个组件,触发beforeRouteUpdate.
5. 解析异步组件(如果有异步组件)
6. 触发全局的beforeResolve.
7. 导航确认,渲染目标组件
8. 触发全局的afterEach
二十二 . 讲一下Vue-Router 几种模式的区别
1.hash模式(默认模式)
- URL中会带有#符号,比如http: //example.com/#/home
- #后面的内容不会被浏览器发送到服务器,只在客户单生效
- 优点: 兼容性好,简单,不需要服务器配置支持
- 缺点: URL不够美观,不利于SEO
2. history模式
- URL 是常规的路径形式,比如http:// example.com/home. 没有#
- 利用HTML5 History API (pushState和popState)来操作浏览器历史记录,实现路由跳转
- 需要服务器配合配置,把所有路由请求都重定向到index.html.否则刷新页面时会404
3.abstract 模式
- 这是一个抽象模式,不依赖浏览器环境,适用于服务器端渲染(SSR)或测试环境
- 不操作浏览器URL,而是用内存记录路由状态
- 主要用于Node.js 服务器端渲染,或者没有浏览器环境的场景
二十三. 讲一下Vuex
Vuex 是Vue的官方状态管理库,用于集中式管理多个组件之间共享的状态(数据),它的理念是: 将组件中可共享的状态抽离出来集中管理,方便维护,调试和统一数据流
1. Vuex 的核心概念
1.state
- 用于存储全局状态,相当于组件中的data
- 所有组件都可以访问 this.$store.state.xxx
- 使用:
//访问全局状态 state export default { computed: { count() { return this.$store.state.count; } } }
使用mapState辅助函数简化
import { mapState } from 'vuex'; export default { computed: { ...mapState(['count', 'userInfo']) } }
2.getters
- 类似于计算属性(computed),可以对state中的数据做派生处理
- 例如格式化日期,过滤列表等
export default { computed: { upperName() { return this.$store.getters.upperUserName; } } } //使用mapGatters简写 import { mapGetters } from 'vuex'; export default { computed: { ...mapGetters(['upperUserName']) } }
3.mutations(同步修改state)
- 唯一可以同步修改state的方法
- 每个mutation是一个函数,必须通过commit来触发
- 所有的state改动都应该通过mutations来追踪,便于调试工具记录和还原
methods: { increment() { this.$store.commit('increment'); } } // import { mapMutations } from 'vuex'; export default { methods: { ...mapMutations(['increment', 'setUserInfo']) } }
4. actions
- 用于处理异步操作,比如发请求,定时器等
- 不能直接修改state,需要在action 中提交mutation
- 通过dispatch触发
methods: { async login() { await this.$store.dispatch('login', { username, password }); } } //使用mapActions import { mapActions } from 'vuex'; export default { methods: { ...mapActions(['login', 'fetchList']) } }
5.modules
- 当状态很多时,可以按功能模块拆分,类似命名空间,避免冲突
- 每个模块都有自己的state,getters,mutations.actions
this.$store.commit('user/setUserInfo', userData);
this.$store.dispatch('cart/addToCart', item);
mapState('user', ['userInfo']),
mapActions('cart', ['addToCart']),
二十四. 如何监听vuex中数据的变化
1. 通过watch 监听Vuex 中的state
2. 使用store.subscribe 监听mutation(全局监听)
在全局注册一次监听所有mutaion调用(比如记录日志):
store.subscribe((mutation, state) => {
console.log('mutation触发了:', mutation.type);
console.log('携带的参数:', mutation.payload);
});
这个可以写在store/index.js里
二十五. 页面刷新后vuex的数据丢失怎么解决?
- 每次获取数据前检测Vuex数据是否存在,不存在则发请求重新拉去数据,存储到Vuex中
- 采用Vuex持久化插件,将数据存储到localStraoge 或者sessionStorage中
二十六. vue3CompositionAPi优势是?
1. 逻辑更清晰,维护更方便
以前用vue2,逻辑按data,methods,watch分散在各个配置项中,组合式APi可以将相关逻辑组织在一起,更清晰易维护
2.更好的代码组织架构(适合大型项目)
可以将业务逻辑封装为独立的函数模块(自定义组合函数useXXX),提高复用性
// useCounter.js
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
在多个组件中复用逻辑:
import { useCounter } from './useCounter'
setup() {
const { count, increment } = useCounter()
return { count, increment }
}
3.类型推导更好,TS支持更强
4. 避免了this 指向问题
- Vue2 里所有属性和方法都通过this访问,容易造成指向混乱和理解困难
- Composition API 直接在setup函数里用变量和函数,减少对this的依赖 ,更直观
5. 兼容Options APi
- 组合式APi不是完全替代,简单组件仍可用Options APi编写,复杂组件可用Composition APi优化逻辑
二十七. 讲一下vue3 和vue2 的区别
1. 响应式系统:
- Vue2 使用的是基于Object.defineProperty的响应式系统,存在一些性能和局限性,比如不能检测数组索引和对象属性新增属性
- Vue3 则使用了基于ES6 Proxy的全新响应式系统,性能更好,支持更全面的响应式跟踪,也更灵活
2. 组合式API
3. 性能优化:
- Vue3 体积更小,启动更快,渲染性能提升
- 内部采用了更搞笑的虚拟DOM重写
4. fragment支持
- Vue2组件模版只能有一个根节点,Vue3支持fragment,可以返回多个根节点,写模版更灵活
5.ts支持
- Vue3 从设计上更好地支持ts,类型推断完善
6.生命周期钩子变化:
- Vue3 中部分声明周期钩子名称发生了变化,比如beforeDestroy 变为beforeUnmount,destroyed变为unmounted
二十八 . v-model 原理? Vue3中的变化?
基本实现原理:
v-model 本质上是一个语法糖,它结合了 v-bind
和 v-on
:
<input
:value="message"
@input="message = $event.target.value"
>
vue3: 支持多个v-model 绑定,语法为v-model:propName
<!-- Vue3 组件 --> 带参数的v-model
<Child v-model:title="title" />// 子组件 props: ['title'], emits: ['update:title'] //通知父组件更新数据
二十九 . vue 组件之间通信方式
1. 父子组件通信
1. 父传子 Porps
2. 子传父 自定义事件
2. 兄弟组件通信
1. Event Bus 全局事件总线 vue3使用mitt 替代
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()// 组件A
EventBus.$emit('message', data)// 组件B
EventBus.$on('message', data => {})
2. 通过共同父组件中转
3.跨级组件通信
1. 依赖于注入 provide/inject
2. $refs