面试备战录

139 阅读5分钟

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(...)。这个函数返回一个PromiseVue Router会等待它resolve出来真正的组件对象。组件加载完成后,再挂载到页面。
    • Webpack 的代码分割(Code Splitting)Webpack在打包时发现了import(),就会自动进行分包。只有访问到对应路由时,浏览器才会请求对应的chunk.js文件。
  • 内部流程简化版
    • 首次进入应用,下载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 组件 / 骨架屏
    • 懒加载太多可能导致频繁的网络请求

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.typevnode.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-showv-if来控制的

3、Vue 中的scoped CSS如何实现样式隔离的?

答:Vuescoped样式隔离是通过在编译阶段为组件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仍然能通过高优先级选择器(或全局样式)覆盖。