1、Vue路由懒加载是如何实现的?原理是什么?
答:Vue Router懒加载基于ES Module动态导入语法。当打包工具(Webpack/Vite)遇到import()时,会自动进行 代码分割,把组件打到单独的chunk文件里。应用运行时,只有访问对应路由时才会真正下载并加载组件。这样能减少首屏 bundle体积,实现按需加载,提升性能。
- 懒加载的由来:如果所有组件都打包到一个
bundle.js,首屏加载会很慢(尤其是大型项目)。懒加载(Lazy Load) 就是:在进入某个路由时,才去加载对应的组件代码。 - 原理
- ES Module 动态导入(Dynamic Import):
import("../views/About.vue")返回一个Promise。浏览器/Webpack在打包时,会把组件单独拆成一个chunk(比如 about.xxx.js)。 - Vue Router 处理:当路由匹配到对应路由,
Vue Router会执行() => import(...)。这个函数返回一个Promise,Vue Router会等待它resolve出来真正的组件对象。组件加载完成后,再挂载到页面。 - Webpack 的代码分割(Code Splitting):
Webpack在打包时发现了import(),就会自动进行分包。只有访问到对应路由时,浏览器才会请求对应的chunk.js文件。
- ES Module 动态导入(Dynamic Import):
- 内部流程简化版
- 首次进入应用,下载
main.js(只包含核心逻辑 + 首页组件)。 - 当用户访问
/about:Vue Router发现component是个函数(异步组件)。- 调用
import()→ 返回Promise。 - 浏览器发起网络请求下载
about.chunk.js。 - 加载完成后
resolve,渲染About组件。
- 首次进入应用,下载
- 优点:
- 减少首屏加载体积(提升首屏速度)。
- 按需加载(用户没点到某个路由,就不会加载它的组件)。
- 结合
webpack魔法注释 可以做预加载 / 预取:
component: () => import(/* webpackChunkName: "about", webpackPrefetch: true */ '../views/About.vue'); - 缺点:
- 首次进入某个懒加载页面时会有短暂的“白屏”(因为要等下载 JS)。通常配合
loading 组件 / 骨架屏。 - 懒加载太多可能导致频繁的网络请求。
- 首次进入某个懒加载页面时会有短暂的“白屏”(因为要等下载 JS)。通常配合
2、keep-alive原理是什么?如何缓存组件?有哪些常见坑?
答:<keep-alive>是 Vue 的抽象组件,用于缓存动态组件,避免重复销毁和创建。内部通过一个缓存对象存储组件实例,并在切换时触发activated/deactivated生命周期,而非unmounted。可以用include/exclude/max控制缓存策略。常见坑包括数据状态残留、组件key不唯一、第三方库副作用以及路由切换注意事项。
keep-alive原理:<keep-alive>是Vue提供的抽象组件,用于对动态组件或路由组件做缓存,避免重复销毁和创建,从而提升性能。- 核心思想:
- 当一个组件被
<keep-alive>包裹时,组件第一次渲染正常挂载。 - 后续切换时,如果组件没有被销毁,而是被缓存起来,只做 激活/停用处理,而不会重新执行
setup / created / mounted。
- 当一个组件被
- 内部实现:
Vue为每个<keep-alive>维护一个缓存对象 cache(通常是 Map 或对象)。key默认使用组件的vnode.type或vnode.key。- 当组件被切换出去:不调用
unmounted,而是调用deactivated钩子,并将VNode缓存。 - 当组件再次激活:从缓存中取出
VNode并插入DOM,同时触发activated钩子。 - 可配合
include/exclude/max属性控制缓存策略。
- 用法
currentView切换不同组件时,已经缓存过的组件不会销毁,状态保持。include:只缓存指定名称的组件;exclude:不缓存指定名称的组件。名称来自name属性(组件名)- 缓存最多
max个组件,超出时会清除最近最久未使用的缓存(LRU 策略)(就是最久未访问的组件)。
<keep-alive include="A,B" exclude="C" :max="3">
<component :is="currentView"></component>
</keep-alive>
常见坑:
- 数据状态残留
keep-alive缓存组件实例,组件内部的响应式数据会一直存在。- 解决方式:在
activated钩子里重置数据,或手动清理。
- 组件
key不唯一- 如果组件
没有key 或 key重复,缓存会混乱 - 建议为动态组件加唯一
:key="xxx"。
- 如果组件
- 第三方库
DOM副作用- 某些依赖
DOM的库(如地图、图表)在组件被缓存后,可能出现DOM丢失或事件绑定异常。 - 需要在
activated钩子里重新初始化DOM。
- 某些依赖
- 子组件也需要缓存
keep-alive只作用于包裹的一级组件,子组件不会自动缓存,需要在子组件上再加<keep-alive>。
- 路由缓存注意
Vue Router使用keep-alive时,路由切换不会触发beforeRouteLeave / beforeRouteEnter等钩子变化,需要结合activated / deactivated使用。
注:这里的切换并不是指的
v-show和v-if来控制的
3、Vue 中的scoped CSS如何实现样式隔离的?
答:Vue的scoped样式隔离是通过在编译阶段为组件DOM元素和CSS选择器都加上一个独有的属性选择器(比如 data-v-xxx)实现的,本质是“命名空间隔离”。它和Shadow DOM不同,不是真正的浏览器作用域隔离,所以仍然可能被外部全局样式覆盖。
核心原理:
- 为当前组件生成一个唯一的特性
(attribute),比如data-v-123abc。 - 编译时,模板里的DOM 元素和样式里的选择器都会自动加上这个标记。
- 最终效果就是:样式只能命中本组件内部的
DOM,不会影响到全局。
<template>
<div class="box">Hello</div>
</template>
<style scoped>
.box {
color: red;
}
</style>
<!-- 渲染结果 -->
<div class="box" data-v-123abc>Hello</div>
<style>
/* 编译后的样式 */
.box[data-v-123abc] {
color: red;
}
</style>
scoped优点:- 使用简单,和
普通CSS写法几乎一样。 - 不需要浏览器额外特性,构建时自动加上作用域。
- 样式默认只影响本组件,避免冲突。
- 使用简单,和
scoped缺点:- 伪类穿透问题:比如
:deep必须显式写出来才能作用于子组件 - 性能问题:因为是通过增加
[data-v-xxx]选择器来实现的,复杂选择器会变长,可能增加样式解析开销。 - 和
Shadow DOM不一样:只是“命名空间隔离”,不是真正的浏览器作用域隔离。外部CSS仍然能通过高优先级选择器(或全局样式)覆盖。
- 伪类穿透问题:比如