返回笔记首页

面试常见追问及回答

主题配置

追问 1:你们的组件库和 Element Plus / Ant Design Vue 有什么区别?

回答思路

不要直接说"没区别"或"抄袭",要强调定制化和业务适配。

标准回答

我们的组件库和这些开源库的定位不同:

1. 业务定制性

  • Element Plus 是通用组件库,我们是针对公司业务深度定制的
  • 举例:我们的 Table 组件内置了权限控制、数据脱敏等业务逻辑
  • 我们的 Form 组件预设了公司的校验规则和字段联动模板
2. 设计规范
  • 我们严格遵循公司的设计系统,颜色、字体、圆角都有自己的标准
  • 品牌色、交互反馈都和公司产品风格保持一致
  • 支持公司的亮色/暗色主题切换
3. 性能优化
  • 针对公司业务场景优化,比如我们的 Table 支持 10 万级数据
  • 打包体积更小,因为没有冗余功能
  • 按需加载做得更彻底,可以细粒度到单个组件
4. 维护成本
  • 开源库更新频繁,可能引入 Breaking Changes
  • 我们的组件库更稳定,API 设计更符合团队习惯
  • 遇到问题可以快速修复,不用等开源社区
5. 技术栈统一
  • 完全基于 Vue3 Composition API,和项目技术栈一致
  • 没有 TypeScript,降低了团队学习成本
  • 代码风格和项目保持一致,易于维护
实际数据
  • 打包体积比 Element Plus 小 40%
  • 首屏加载时间快 30%
  • 组件数量控制在 40+ 个,都是高频使用的

追问 2:虚拟滚动的原理是什么?为什么能提升性能?

标准回答

核心原理:只渲染可视区域的 DOM

传统方式的问题:

  • 1 万行数据 = 创建 1 万个 DOM 节点
  • 每个节点监听事件、计算样式、布局、绘制
  • 浏览器渲染负担重,卡顿严重

虚拟滚动的做法:

  1. 计算可视区域:根据容器高度和滚动位置,计算应该显示第几行到第几行
  2. 只渲染可见行:比如屏幕只能显示 20 行,那就只创建 20 个 DOM
  3. 动态更新:滚动时,动态替换这 20 个 DOM 的内容
  4. 撑开容器:用一个空 div 撑开总高度,保证滚动条正常
性能提升的关键点
  1. DOM 数量减少
    • 从 1 万个降到 25 个(20 可见 + 5 缓冲)
    • DOM 操作耗时从 3s 降到 80ms
  2. 内存占用降低
    • 不需要缓存所有 DOM 节点的状态
    • 内存从 500MB 降到 50MB
  3. 重排重绘减少
    • 滚动时只是移动位置(transform),不触发 layout
    • 用 CSS transform 而不是 top,启用硬件加速
  4. 事件监听优化
    • 只给 20 个 DOM 绑定事件,不是 1 万个
    • 用事件委托进一步优化
我的优化细节
  • 使用 requestAnimationFrame 节流滚动事件
  • 添加缓冲区(上下各 5 行),避免快速滚动白屏
  • 使用二分查找(O(log n))定位可视区域,不是遍历(O(n))
  • ResizeObserver 监听高度变化,支持动态高度
实测数据
  • 10 万条数据,渲染时间从 15s 降到 150ms
  • 滚动帧率稳定在 60fps
  • 内存占用减少 90%

追问 3:为什么选择 Vite 而不是 Webpack?

标准回答

选择 Vite 主要基于三个考虑:

1. 开发体验

Webpack 的问题:

  • 冷启动需要打包整个项目,我们项目启动要 40 秒
  • 热更新慢,改一行代码要等 3-5 秒
  • 开发效率低,影响心情

Vite 的优势:

  • 基于 ESM,按需加载,启动只需 2 秒
  • HMR 极快,热更新在 300ms 内
  • 真正做到"所见即所得"
2. 构建性能

Vite 使用 Rollup 打包:

  • Tree Shaking 效果更好,打包体积小 30%
  • 支持多种格式输出(ESM/CJS/UMD)
  • 插件生态完善,易于扩展

对比数据:

  • Webpack 构建时间:45 秒
  • Vite 构建时间:12 秒
  • 快了 3.75 倍
3. 技术趋势
  • Vite 是 Vue 官方推荐的构建工具
  • 主流框架都在拥抱 ESM(React、Svelte)
  • 学习成本低,配置简单
遇到的问题和解决
  1. 兼容性问题
    • Vite 默认不支持旧浏览器
    • 解决:用 @vitejs/plugin-legacy 生成 polyfill
  2. 第三方库兼容
    • 有些老库不支持 ESM
    • 解决:在 optimizeDeps 中预构建
  3. CSS 处理
    • 需要配置 Less、PostCSS
    • 解决:vite.config.js 中配置 preprocessorOptions
实际效果
  • 开发效率提升 50%
  • 团队反馈非常好,不用再等待漫长的编译

追问 4:CSS Variables 和 Less 变量有什么区别?为什么两者都要用?

标准回答

CSS Variables 和 Less 变量的区别

特性 CSS Variables Less 变量
执行时机 运行时 编译时
是否可动态修改 是,JS 可修改 否,编译后固定
浏览器支持 现代浏览器 编译后支持所有
继承性 可继承 无继承
计算能力 受限 强大(函数、运算)
为什么两者都要用
CSS Variables 用于
  1. 主题切换
css
:root {
  --primary-color: #1890ff;
}
[data-theme="dark"] {
  --primary-color: #177ddc;
}
  • 运行时切换,零成本
  • 用户可以实时看到效果
  1. 动态定制
javascript
document.documentElement.style.setProperty('--primary-color', '#ff0000')
  • 允许用户自定义品牌色
  • 不需要重新编译
Less 变量用于
  1. 复杂计算
less
@base-size: 14px;
@large-size: @base-size * 1.2;  // 16.8px
  • 编译时计算,性能更好
  1. 颜色函数
less
@primary-color: #1890ff;
@hover-color: lighten(@primary-color, 10%);  // 变亮 10%
  • 生成颜色阶梯
  1. Mixin 和函数
less
.button-variant(@color) {
  background-color: @color;
  border-color: @color;
  &:hover {
    background-color: lighten(@color, 10%);
  }
}
  • 复用样式逻辑
我们的使用策略
  1. 全局设计变量用 CSS Variables
    • 颜色、字体、间距、圆角等
    • 需要运行时修改的
  2. 编译时计算用 Less 变量
    • 尺寸比例计算
    • 颜色阶梯生成
    • 复杂的样式逻辑
  3. 互相配合
less
@primary-color: var(--primary-color);  // 从 CSS Variables 读取

.button {
  background: @primary-color;
  &:hover {
    background: lighten(@primary-color, 10%);
  }
}
实际效果
  • 主题切换流畅无卡顿
  • 代码复用度高,维护成本低
  • 既有灵活性又有性能保障

追问 5:组件库如何保证向后兼容?如果要做 Breaking Change 怎么办?

标准回答

向后兼容是组件库的核心承诺,我们从三个层面保障:

1. API 设计层面

遵循"宽进严出"原则:

  • Props 使用 validator 校验,但允许降级
  • 不轻易删除 Props,用 deprecated 标记
  • 新功能用新 Props,不改老 Props 的行为

举例:

javascript
props: {
  // 旧版本
  size: {
    type: String,
    validator: (v) => ['small', 'large'].includes(v)
  },

  // 新版本(兼容旧版本)
  size: {
    type: String,
    validator: (v) => ['small', 'medium', 'large'].includes(v),
    default: 'medium'  // 新增默认值,兼容旧版本
  }
}
2. 版本管理层面

使用 Semantic Versioning:

  • 1.0.01.0.1:Bug 修复,完全兼容
  • 1.0.01.1.0:新功能,向后兼容
  • 1.0.02.0.0:Breaking Change,不兼容

用 changeset 自动化管理:

bash
# 提交代码时标记变更类型
pnpm changeset
# 选择 patch / minor / major

# 自动生成 CHANGELOG
pnpm version-packages

# 发布
pnpm release
3. 弃用流程

如果必须做 Breaking Change:

第一阶段:标记弃用(Deprecation)
javascript
props: {
  oldProp: {
    type: String,
    validator: (v) => {
      console.warn('[Deprecated] oldProp will be removed in v2.0, use newProp instead')
      return true
    }
  }
}
第二阶段:提供迁移指南
  • 在文档中详细说明如何迁移
  • 提供 codemod 脚本自动化迁移
  • 给业务团队充足的时间(至少 3 个月)
第三阶段:发布新大版本
  • 主版本号升级(v1.x → v2.0)
  • CHANGELOG 详细列出所有 Breaking Changes
  • 提供回退方案(如果需要)
实际案例

我们的 Table 组件优化:

  • v1.0:支持 datacolumns props
  • v1.5:新增 virtual prop 支持虚拟滚动,完全向后兼容
  • 旧代码无需修改,新代码可以选择开启虚拟滚动
保障措施
  1. 自动化测试:回归测试保证不破坏旧功能
  2. 持续集成:每次提交都跑完整测试
  3. 业务验证:新版本先在测试环境验证
  4. 灰度发布:先在 1 个系统试点,再全面推广
数据支撑
  • 组件库迭代 12 个版本,0 次 Breaking Change
  • 6 个业务系统升级顺利,无兼容性问题
  • 团队满意度 95%+

追问 6:组件库的 Tree Shaking 是如何实现的?

标准回答

Tree Shaking 是减小打包体积的关键,我从三个层面实现:

1. 构建配置

关键点:

  • 使用 ES Module 格式(import/export
  • Rollup 配置 preserveModules: true
  • 标记副作用文件
javascript
// rollup.config.js
export default {
  output: {
    format: 'es',
    preserveModules: true,  // 保留模块结构
    dir: 'es'
  },
  external: ['vue'],  // 不打包 Vue
  treeshake: {
    moduleSideEffects: false  // 假设模块无副作用
  }
}

// package.json
{
  "sideEffects": [
    "*.css",
    "*.less",
    "*.vue"  // 这些文件有副作用,不能 tree-shake
  ]
}
2. 组件独立导出
javascript
// packages/components/index.js

// ❌ 不好的做法
export default {
  Button,
  Input,
  Table
}

// ✅ 好的做法
export { Button } from './button'
export { Input } from './input'
export { Table } from './table'

这样打包工具可以分析依赖:

javascript
// 用户代码
import { Button } from 'vue-ui'  // 只引入 Button
// 打包工具会 tree-shake 掉 Input、Table
3. 样式按需加载

问题:直接在组件中 import './style.css' 会导致样式无法 tree-shake

解决方案:

javascript
// 组件代码(不引入样式)
export default {
  name: 'VButton'
}

// 单独的样式入口
// packages/components/button/style.js
import './style.css'

// 用户按需引入
import { Button } from 'vue-ui'
import 'vue-ui/es/button/style'
4. Babel 插件自动化

手动引入样式太麻烦,提供 Babel 插件:

javascript
// babel-plugin-import 配置
{
  "plugins": [
    ["import", {
      "libraryName": "vue-ui",
      "libraryDirectory": "es",
      "style": true  // 自动引入样式
    }]
  ]
}

// 用户代码
import { Button, Input } from 'vue-ui'

// 自动转换为
import Button from 'vue-ui/es/button'
import 'vue-ui/es/button/style'
import Input from 'vue-ui/es/input'
import 'vue-ui/es/input/style'
验证 Tree Shaking 效果
  1. 分析打包产物
bash
# 使用 rollup-plugin-visualizer
pnpm build --report

# 查看每个模块的大小
  1. 对比数据
    • 全量引入:800KB(gzip 后 280KB)
    • 按需引入 5 个组件:120KB(gzip 后 40KB)
    • Tree Shaking 生效率:85%
常见问题
  1. CommonJS 格式不支持 Tree Shaking
    • 解决:提供 ES Module 格式
  2. 默认导出(export default)影响 Tree Shaking
    • 解决:使用命名导出(export { xxx })
  3. 副作用代码被误删
    • 解决:在 package.json 标记 sideEffects
最佳实践
  • 优先使用 ESM 格式
  • 避免全局副作用代码
  • 组件独立,减少相互依赖
  • 提供自动化工具降低使用成本