Vue 的响应式原理
首先给对象设置代理拦截,如果发生对象取值行为(getter 被调用),可以在这里收集依赖(通常是组件)。当数据发生赋值(setter被调用),之前收集的依赖都会被执行。vue3中effect发生取值行为,对应的属性会收集该effect。如果该数据发生变化那么去根据 对象=>属性=>[effect,effect] 来找到对应所有effect执行,effect.run()
你如何避免 Vue 的性能问题,尤其是当数据结构变得很复杂时?
当数据结构比较复杂时,无论是Proxy或者Object.defineProperty都需要递归来代理对象或属性,这样就会变得很耗时,优先考虑简化数据结构。也可以使用shadowReactive、shadowRef浅层代理的方式来优化。
Vue 组件的生命周期有哪些阶段?每个阶段的作用是什么?
-
创建阶段:
vue2
beforeCreate:组件实例化后,数据观测和事件配置之前调用。此时,组件的data和methods等还未初始化。created:组件实例化完成后调用,此时data已经初始化,computed和watchers也已设置,但还没有挂载到 DOM。可以在此阶段进行一些数据初始化工作、API 请求等。
vue3
setup:替代了beforeCreate和created
-
挂载阶段
beforeMount:组件挂载之前调用,此时template已经被解析为虚拟 DOM,el还未插入到页面上。mounted:组件挂载完成后调用,此时组件已经挂载到真实 DOM 中。可以在这里进行 DOM 操作、初始化第三方插件、发送网络请求等
-
更新阶段
beforeUpdate:组件数据更新之前调用。此时data已经更新,但视图还未更新。可以在这里进行优化,例如缓存一些数据,避免不必要的计算。updated:组件数据更新之后调用,此时组件的视图已更新。可以在这里执行依赖 DOM 变化的操作,如滚动位置调整、第三方库更新等。
-
销毁阶段
beforeUnmount(以前是beforeDestroy):组件销毁之前调用,可以在这里进行清理操作,例如移除事件监听、取消定时器等。unmounted(以前是destroyed):组件销毁之后调用,此时组件的 DOM 已经从页面中移除。可以在这里执行最后的清理工作,如移除全局事件监听器、销毁外部资源等。
什么是KeepAlive
KeepAlive 是 Vue 内置的抽象组件,通过缓存组件实例(基于 LRU 策略)避免重复渲染,提升性能并保留用户状态。其工作流程围绕缓存管理展开,支持通过 include/exclude 和 max 精细控制缓存策略。优势在于优化高频切换场景的体验与性能,但需权衡内存占用,合理配置参数以避免潜在问题。
KeepAlive会新增两个生命周期钩子,与普通钩子的触发逻辑不同,当组件从缓存中被激活。会触发 activated ,当组件被停用时触发 deactivated;如果是首次加载并未存在缓存,则跟普通组件生命周期相同
解释 created 和 mounted 钩子函数的区别
created是数据,虚拟dom等相关准备工作已经完成,但是虚拟dom并未真实的挂载到页面上。mounted生命周期是已经将真实的虚拟dom挂载到节点上。一般情况下会在mounted去操作进行dom操作,在created阶段进行数据请求操作
计算属性和普通方法有什么区别?能否举例说明?
计算属性当依赖的响应式数据发生变化时,它的set或者回调函数会被执行。一般情况下当我们依赖某个响应式数据来得出某项结果时,会使用计算属性。但是计算属性性能比较差,只要内部依赖的数据发生变化都会执行。使用过多可能会导致大量没有意义的计算产生。
普通函数是定义的一般函数,只有在用户调用时才会执行。并没有跟响应式数据相关联。
普通开发中能使用普通函数,尽量使用普通函数,避免使用计算属性。这样产生的副作用是可控的,如果需要处理数据,可以在需要处理数据的时机调用普通函数处理即可。
watch 和 computed的区别
computed支持缓存,相依赖的数据发生改变才会重新计算;watch不支持缓存,只要监听的数据变化就会触发相应操作
computed不支持异步,当computed内有异步操作时是无法监听数据变化的;watch支持异步操作
什么是 Vue 中的虚拟 DOM?它如何提高渲染性能?
在vue中一个个描述真实dom节点的javascript对象称之为虚拟dom,结合 Diff 算法 和 批量更新策略,最小化对真实 DOM 的直接操作,从而提升渲染性能。
- 性能优化:减少高频操作 DOM 的开销;
- 跨平台:为服务端渲染、原生应用等场景提供基础。例如uniapp等
- 开发友好:让开发者专注数据逻辑,无需手动优化 DOM;
什么是“diff 算法”?Vue 是如何优化 diff 算法的?
diff算法是当组件依赖的响应式数据发生变化需要操作dom时的一种减少dom操作的优化算法。
更新 patch() diff算法(平级比较、不涉及到跨级比较。全量比对)
- 之前和之后的元素不是同一个节点(tag,key),删除老的换新的
- 是同一个节点、会复用节点,之后更新属性和子节点
- 字节点更新有九种方式(双方子节点都是列表的情况)
- 同序列比较来进行了优化syncfromstart/sync from end(挂载、卸载)
- 确定两个序列中变化的部分。 根据新节点创建映射表,用来的节点去找,如果新的中没有则制除、有的就复用比较属性和子节点
什么是自定义指令
自定义指令是对 DOM 元素的精细控制,用户可以把一些直接的 DOM 操作封装成指令,使得模板更加简洁。常见的用途有事件监听、动态控制样式、组件传递数据。
自定义指令分为可以注册为全局指令、或者局部指令。
指令在操作 DOM 时也有相应的生命周期,跟vue普通组件创建、挂载、更新、销毁的生命周期一致。
如何创建一个自定义指令?能否举个例子说明它的使用场景?
在日常项目中,button或者列表元素在active、hover时会UI会有不同样式样式变化。这时可以考虑使用自定义指令来监听元素的事件,给元素添加hover效果等
Vue 3 和 Vue 2 在响应式系统上有哪些不同?
首先在实现代理拦截时不同,vue2使用的是Object.defineProtype而vue3中使用的是Proxy代理,vue2中需要代理每个对象的每一个属性,性能消耗巨大。vue3中使用proxy直接代理对象,不用去遍历每个属性。依赖收集vue3比vue2是更简单一些。
你能详细解释 Vue 3 中的 Composition API 吗?它与 Options API 有什么区别?
vue2中组件是使用Options API编写的,是通过选项对象来定义组件的各种属性,比如data、methods、computed、watch等等。这种方式的优点是结构清晰,每个选项对应不同的功能,容易理解和组织代码。但缺点是组件变得复杂时,相关的逻辑可能会分散在不同的选项中,导致代码难以维护和复用,修改代码需要来回跳转
composition API可以把项目中的功能封装成方法,用户使用的时候可以按需导入,提升复用性;并且功能更加内聚,不会像optionsApi中那样反复横跳。并且vue内部提供的Api都是composition API
ref 和 reactive 对比
在 Vue3 的响应式系统中,ref 和 reactive 均基于 Proxy 实现,但适用场景有所区别:
ref 作为通用响应式容器,通过 RefImpl 类包装数据,支持基本类型和对象类型,若值为对象则内部自动调用 reactive 进行深层代理,访问时需通过 .value 语法(略显冗余);reactive 专为对象/数组设计,通过 Proxy 直接代理对象属性,无需 .value 即可直接操作。
基本类型优先用 ref,对象类型优先用 reactive,既保证类型适用性,又能减少 .value 带来的代码冗余。
如何优化 Vue 应用的性能?你使用过哪些性能优化技巧?
vue应用的性能优化一般分为代码质量、网络传输、运行时性能、构建效率这四个方面。
在代码质量方面
- 可以减少渲染次数例如注意
v-if和v-show的时机; - 合理的拆分组件,尽量做到职责单一,原子化;
- 合理的使用响应式数据,减少
computed和watch的使用,这些都是基于effect副作用函数实现,会直接影响性能
网络传输主要包含懒加载、CDN、资源压缩、缓存
- 包含但不限于图片懒加载、组件懒加载等只要数据较大或不是核心模块都可以采用懒加载的模式;
- 使用CDN来提高资源访问速度;
- 压缩资源来减少资源体积。
- 增加前端缓存、网络缓存等
运行时性能是解决渲染跟其他渲染主线程任务产生冲突造成的卡顿问题
- 例如大数据渲染页面,可以采用虚拟滚动或者
canvas绘制的方式; - 操作dom元素时要读写分离,在获取某些dom元素属性值会强制重新计算样式
构建效率优化
- 选一个高效率的打包工具(vite)
- 支持tree-shaking的方式减少无用代码体积
- 按需加载动态导入组件,避免一次性导入组件减少首屏加载时间
- 开启代码压缩
- 使用代码分割,按需拆分较大的文件
在一个大型 Vue 应用中,如何进行按需加载(lazy loading)和代码拆分?
- Vue Router 懒加载(路由懒加载)实力
- 动态导入异步组件
- 按需加载第三方库
- vue3
defineAsyncComponent提供超时处理的异步加载方式
Vue Router 的工作原理是什么实力
前端路由实现方案有两种
- hash模式:
hashchange监听url变化,页面刷新不会向服务端发请求,也不会支持服务端渲染。不能做SEO优化 - history模式:
pushState; 刷新页面会向服务端发请求,如果资源不存在出现404;一般会渲染首页,由首页根据路径重新渲染
工作原理是通过页面URL的变化来修改path对应组件内容。如果浏览器的兼容性比较好可以只使用pushState来做hash模式的跳转,需要注意在实现时参数不同。
路由匹配是如何执行的?你能解释一下 Vue Router 的匹配过程以及它是如何选择路由的?
当监听到路由发生变化时,会获取对应的path及参数,然后跟路由表进行比对,找到最佳匹配的路由,并动态渲染组件; 具体匹配过程会按照精确路由匹配、动态路由匹配、嵌套路由匹配、通配符匹配依次来进行匹配组件渲染;如果未匹配到则不渲染
解释 Vue Router 是如何实现路由懒加载(lazy loading)的?有什么优缺点?
路由懒加载(Lazy Loading)是指 按需加载路由组件,只有当用户访问某个路由时,才会加载对应的组件,而不是在应用初始化时一次性加载所有组件。默认情况下,Vue Router 在项目启动时会把所有路由组件打包进主 bundle.js,导致首屏加载变慢。懒加载能优化这一点,按需加载组件,提高应用的性能。
Vue Router 通过 import() 结合 打包工具代码分割实现懒加载。
-
优点:减少首屏加载时间、降低内存占用、提升应用性能。
-
缺点:首次加载某个页面时可能会有轻微的网络延迟。
-
优化方案:结合 预加载(Prefetch) 和 骨架屏 提高用户体验。
Vue Router 中路由守卫了解吗?如何利用这些守卫进行路由权限控制?
路由守卫分为,全局守卫、路由独享守卫、组件内守卫;
- 全局守卫:
beforeEach、beforeResolve、afterEach - 路由独享守卫:
beforeEnter - 组件内守卫:
beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
这些导航守卫执行顺序如下
导航触发
↓
调用全局 `beforeEach`
↓
调用路由配置的 `beforeEnter`
↓
调用组件内 `beforeRouteEnter`
↓
解析异步路由组件
↓
调用组件内 `beforeRouteUpdate`(如果复用)
↓
调用全局 `beforeResolve`
↓
导航确认
↓
调用全局 `afterEach`
如果需要利用守卫做权限控制,需要考虑当前权限是加到全局、路由跳转、组件内部;开发者需要根据业务情况的不同选择不同的导航守卫;如果做全局登录权限可以在beforeEach中添加相应的逻辑,来控制全局用户登录权限。
如果需要在beforeEach中执行异步操作,需要确保异步操作完成后,调用next即可。但这是一个全局路由,每次路由跳转都要执行,而异步操作是否需要每次执行,可以结合业务来进行合理的优化。如果异步未完成,用户离开页面,也可以通过返回函数来取消异步的执行
你如何实现 Vue 中的嵌套路由?嵌套路由的使用场景和优势是什么?
嵌套路由是 Vue Router 中组织复杂页面结构的核心工具,通过父子路由的层级关系实现布局复用、模块化开发和直观的 URL 设计。适用于多层级导航、共享布局、动态路由等场景,能显著提升代码可维护性和用户体验。合理使用嵌套路由,可让项目结构更清晰,同时支持灵活的功能扩展。
如何实现动态路由添加?举例说明在运行时添加路由的场景和实现方法。
在vue中可以使用addRoute来动态添加路由,这样会在路由匹配记录中动态增加一条记录。一般情况下在做权限校验会根据用户不同的权限来添加不同的路由。
vue-router实现的原理是监听url的变化,动态的在路由记录中去匹配对应的组件渲染到页面上。在使用的时候会通过createRouter来按照相应的规则生成对应的匹配记录,生成路由记录的时候也是用的addRoute添加记录。这里只不过将此API暴露给用户,方便实现动态添加的功能
如何使用懒加载模块和动态路由结合来优化性能。
路由懒加载是在配置路由时使用import函数动态导入,配合构建工具代码分割,按照模块将blundle.js,拆分成一个个单独的文件。动态路由则是通过用户或者不同的业务需要,来动态添加路由。
减少首屏资源体积,按需加载非关键模块;模块化路由配置,便于扩展和维护;独立模块可长期缓存,提升重复访问速度。
Vue Router 的懒加载是如何影响 SEO 的,如何优化?
影响
- 搜索引擎(爬虫)无法执行
js或者等待异步执行,导致懒加载的内容未检索到; - 如果页面的标题或者描述等相关字段在懒加载组件中生成也无法被检索到;
- SPA(单页应用)的前端路由会被搜索引擎视为单一页面,导致多页面内容无法独立索引。
如何解决
- 使用
ssr生成完整的HTML - 静态站点可以直接使用相关工具生成静态页面访问(
SSG) - 避免在懒加载组件中设置
SEO相关信息,可以路由守卫中设置
什么是 Vue Router 的“预加载”?
在路由空闲时间去动态加载模块(需要配置懒加载,不然所有模块都在主包中,一次性加载完成)。
webpack实现
import(/* webpackPrefetch: true */'./component.vue')
vite实现
// 页面渲染完成后调用
const routes = router.getRoutes()
if(routes){
routes.forEach((route)=>{
setTimeout(()=>{
route.components.default()
})
})
}
在进行服务器端渲染时,如何处理客户端和服务端的路由同步?
-
确保服务端和客户端共用同一个路由配置(如 React Router、Vue Router)。
-
服务端匹配路由并正确渲染组件,使用
StaticRouter(React)或MemoryHistory(Vue)。 -
客户端 Hydration 确保状态一致,避免二次请求或数据闪烁。
-
预加载数据并注入到
window.__INITIAL_STATE__,防止不必要的 API 请求。 -
避免 SSR/CSR 结果不匹配,避免
useEffect或onMounted影响 SSR 结果。 -
正确处理 404 和重定向,返回正确的 HTTP 状态码。