ZDecode
笔记
博客
Search
⌘
K
个人资料
个人优势
工作经历
项目经历
专业技能
完整纯文本
简历配套面试题 — v1.5 基于 v1.5.mdx 简历中的具体技术栈与项目经历整理。 答案默认折叠,点击展开。建议面试前自测,能用自己的话说出来再展开核对。 一、Vue 深度 Q1: Vue 2 和 Vue 3 响应式实现的核心差异是什么?为什么 Vue 3 选择重写? 📖 参考答案 / 解析Vue 2 使用 Object.defineProperty 劫持对象属性的 getter/setter: 必须递归遍历对象所有属性进行劫持(初始化慢) 无法监听新增 / 删除属性(需要 Vue.set / Vue.delete) 无法监听数组下标修改 / length 变化(要重写 7 个数组方法) Vue 3 改用 Proxy: 拦截整个对象的访问,懒代理(访问到才递归),性能更好 天然支持新增 / 删除属性、数组下标 支持 Map / Set / WeakMap 等集合类型 为什么重写:Object.defineProperty 的局限是语言层面的,无法绕过;而 Proxy 是 ES6 新特性,Vue 3 不再兼容 IE 11,可以放心用。 注意点:Proxy 是浅层的,访问深层属性时才会再次代理 → Vue 3 通过这个实现了懒代理。 Q2: Composition API 解决了 Options API 哪些问题?什么时候不该用? 📖 参考答案 / 解析Options API 的痛点: 逻辑碎片化:一个功能的代码分散在 data / computed / methods / watch 中 复用差:Mixin 有命名冲突、来源不清的问题 TypeScript 友好度差:this 类型推导复杂 Composition API 的优势: 按功能组织代码(useXxx 一个 hook 集中相关逻辑) 复用通过普通函数实现,类型推导自然 更适合大型项目 什么时候不该用: 简单组件(< 50 行)用 Options API 反而更直观 团队没 TS 基础时,Composition API 的优势不明显 老项目大面积迁移成本高,不必为了用而用 陷阱:ref vs reactive 的选择、toRefs 解构问题、watch vs watchEffect。 Q3: 你在简历里提到「定位并解决 Vue 插件引起的重复渲染问题」,具体怎么排查的? 📖 参考答案 / 解析排查路径: 复现:Vue DevTools 打开"Highlight Updates",肉眼观察哪些组件在不该重渲染时高亮 定位触发源: 是 props 变化?用 watch 监听 props 看是不是引用变了 是 state 变化?看响应式数据是不是被插件意外触发 是父组件 re-render?用 React.memo 类比手法定位 常见原因: 插件在 setup 中创建了响应式对象,每次渲染都重新创建 → 引用不稳定 插件提供的 provide 值是新对象引用 → 子组件 inject 后认为变了 全局状态 mutate 而不是 replace 解决: 把对象提到 setup 外或用 shallowRef / markRaw 用 computed 缓存 改用 Pinia 的 storeToRefs 而不是解构 总结:核心是"引用稳定性"和"响应式追踪范围",DevTools 是最快的工具。 Q4: Pinia 比 Vuex 好在哪?项目里你是怎么组织 store 的? 📖 参考答案 / 解析Pinia 优势: 去除 mutation:直接改 state,更符合 Composition API 直觉 TS 友好:自动推导 state / getters / actions 类型,零类型注解 模块化天然:每个 store 是独立函数,不需要 modules 轻量:~1KB DevTools 集成:time-travel、热重载 组织方式(个人实践): stores/ user.ts // 用户身份、权限 app.ts // 全局配置、主题 modules/ chat.ts // 实时聊天状态 game.ts // 游戏运行时状态 最佳实践: 小 store 优于大 store(单一职责) Action 中 await 调用其他 store 是允许的 跨 store 引用用 useOtherStore() 而不是直接 import 实例(避免循环依赖) 持久化用 pinia-plugin-persistedstate Q5: Vue 3 有哪些性能优化点?你用过哪些? 📖 参考答案 / 解析框架层面: Patch Flag:编译时标记动态部分,运行时只 diff 动态节点 Block Tree:跳过静态子树 静态提升 (hoistStatic):静态节点提到 render 外 预字符串化:连续静态节点编译为字符串 应用层面(项目实践): v-memo 缓存大列表项 shallowRef / shallowReactive 处理大对象 markRaw 标记不需要响应式的对象(如 Phaser 实例、Cesium 实例) 组件懒加载 defineAsyncComponent KeepAlive 缓存不常切换的页面 长列表用虚拟滚动(vue-virtual-scroller) 调试工具:Vue DevTools Performance 面板 + Chrome Performance。 二、React 生态 Q6: React Hooks 闭包陷阱怎么理解?有哪些典型场景? 📖 参考答案 / 解析闭包陷阱本质:每次 render 都创建新的 props/state 快照,函数体内的变量绑定到当时那次 render 的值;如果在 setTimeout / setInterval / useEffect 中引用,可能用到过期数据。 典型场景 const [count, setCount] = useState(0) useEffect(() => { const id = setInterval(() => { console.log(count) // 永远是 0 }, 1000) return () => clearInterval(id) }, []) // 空依赖 → count 被锁定在初始值 解决方案: 加依赖:useEffect(..., [count]) —— 但会反复创建定时器 函数式更新:setCount(c => c + 1) useRef 持有最新值: const countRef = useRef(count) useEffect(() => { countRef.current = count }) // 在 setInterval 中读 countRef.current 自定义 useEvent / useEventCallback hook 包装 Q7: React Query 解决了什么问题?和 Redux / Zustand 是什么关系? 📖 参考答案 / 解析React Query 定位:服务端状态(Server State)管理库,不是通用状态管理。 它解决的问题: 接口请求的 loading / error / data 状态样板代码 缓存(同一接口短时间内不重复请求) 自动重试、轮询 失效(mutation 后让相关 query 重新拉取) 后台刷新(refetchOnWindowFocus) 分页 / 无限滚动 与 Redux / Zustand 关系: Redux / Zustand 管的是客户端状态(UI 状态、表单、用户偏好) React Query 管的是服务端状态(拉来的数据) 两者互补不冲突,大型项目通常同时存在 项目实践(针对简历过敏性疾病随访项目): 后台列表 / 详情用 Vue Query 自动管理缓存与失效 全局用户身份用 Zustand 即时通讯状态用 Zustand + 本地持久化 Q8: Zustand 的设计哲学是什么?为什么比 Redux 简洁那么多? 📖 参考答案 / 解析核心哲学: No Provider:不需要 <Provider> 包裹,全局单例 Hook-first:通过 useStore 直接订阅 极简 API:一个 create 函数搞定 state + actions 选择性订阅:组件只订阅自己关心的 slice,避免无关 re-render 核心代码示意: const useBearStore = create((set) => ({ bears: 0, increase: () => set((state) => ({ bears: state.bears + 1 })), })) // 组件中 const bears = useBearStore((s) => s.bears) 对比 Redux: Redux:action / reducer / dispatch / selector 四件套,模板代码多 Zustand:直接 set 更新,函数式或对象式都行 适用场景:中小型应用 / 局部状态(聊天室、游戏状态)。大型有复杂中间件需求(撤销重做、time-travel debug)的,Redux Toolkit 仍有优势。 三、TypeScript Q9: 你在 uni-app 项目中怎么引入 TypeScript?踩过什么坑? 📖 参考答案 / 解析接入步骤: 升级 uni-app 到支持 TS 的版本(CLI 项目用 vite 模板) 添加 tsconfig.json,关键配置: { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "node", "strict": true, "jsx": "preserve", "types": ["@dcloudio/types", "@types/wechat-miniprogram"] } } 安装类型声明:@dcloudio/types(uni 全局 API)、@types/wechat-miniprogram(wx 全局 API) vue 文件添加 <script setup lang="ts"> 踩过的坑: 多端条件编译类型不一致:#ifdef MP-WEIXIN 在 H5 端类型缺失 → 用 // @ts-ignore 或自定义条件类型 小程序组件 props 类型:自定义组件 defineProps 需要手动声明 uni.xxx API 在新版本类型不全:自己补充 .d.ts Pinia + uni 持久化:小程序没 localStorage,需自定义 storage adapter,类型需对齐 收益:类型安全 + IDE 自动补全,大型项目几乎必备。 Q10: 写一个 TS 工具类型 DeepReadonly<T>,把对象所有嵌套属性变为只读。 📖 参考答案 / 解析type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? T[K] extends Function ? T[K] : DeepReadonly<T[K]> : T[K] } 关键点: 用映射类型 [K in keyof T] 遍历 加 readonly 修饰 用条件类型判断是否为 object,递归 排除 Function(函数也是 object,但不需要递归) 进阶:还要排除 Date / RegExp / Map / Set: type Primitive = string | number | boolean | bigint | symbol | null | undefined type Builtin = | Primitive | Function | Date | Error | RegExp | Map<any, any> | Set<any> type DeepReadonly<T> = T extends Builtin ? T : { readonly [K in keyof T]: DeepReadonly<T[K]> } 实战用途:配置对象、状态快照防篡改。 四、工程化 / Monorepo Q11: 简历提到「20+ 游戏包 / 5min → 2min」,构建优化具体怎么做的? 📖 参考答案 / 解析优化路径(按收益排序): Monorepo 工具链选型:从 Lerna 改为 pnpm workspace + 增量构建工具(Turborepo / Nx) pnpm 用硬链接,安装速度 + 磁盘占用都改善 构建缓存: Turborepo / Nx 的远端缓存:未改动的包直接命中缓存,跳过构建 Vite 的依赖预构建 + 缓存 并行构建: Turborepo 自动按依赖图并行 CI 中按 affected packages 只跑增量 抽取公共依赖: 把 Vue3 / Pinia / 公共 UI 库放到 packages/shared 避免每个游戏包重复打包同一份 vendor Source Map 策略: 开发用 cheap-module-source-map,生产用 hidden-source-map Tree-shaking: ESM-only 依赖、sideEffects: false 按需引入 Vuetify / Element-Plus 衡量:用 vite-plugin-inspect / webpack-bundle-analyzer 看产物分布。 Q12: Babel 插件开发流程是怎样的?你做过哪些插件? 📖 参考答案 / 解析流程: AST 入门:用 AST Explorer 看代码对应的 AST 树 Babel 插件结构: module.exports = function ({ types: t }) { return { name: 'my-plugin', visitor: { ImportDeclaration(path, state) { // 操作 path.node }, }, } } 常用 API:path.replaceWith / path.remove / t.callExpression... 测试:用 babel-plugin-tester 实战插件类型: 按需加载:Element-Plus / Vuetify 等组件库自动导入对应 css 国际化:把硬编码字符串提取到 i18n 资源文件 自定义指令转换:把 v-permission 编译为函数调用 生产环境去 log:删除 console.log autoImport:分析模板,自动 import 用到的组件 踩坑: Babel 7 vs 6 不兼容 path.traverse 容易死循环(要用 path.skip() 或 state.processed = true) 遇到 import('xxx') 动态导入要特殊处理 Q13: Vite 比 Webpack 快在哪?有哪些场景 Vite 不适合? 📖 参考答案 / 解析Vite 快的原因: 开发模式用 ESM:浏览器原生支持 ESM,不需要打包,按需编译 依赖预构建用 esbuild:把 CommonJS 转 ESM、合并依赖,esbuild 是 Go 写的,比 babel/webpack 快 10-100 倍 HMR 精准:模块图清晰,改一个文件只重编译这个文件 Webpack 快的地方: 生态成熟、配置灵活 老项目兼容性好 复杂 SSR / 多入口场景配置更完善 Vite 不适合的场景: 需要兼容老浏览器(IE 11 等):Vite 默认输出现代 ES,需要 @vitejs/plugin-legacy 特别复杂的构建逻辑:Vite 的 Rollup 插件生态比 Webpack 弱 SSR 高度定制:Vite SSR 模板还在演进 微前端 qiankun 等:Vite 5 + qiankun 仍有兼容问题,需要 vite-plugin-qiankun 实际选择: 新项目 → Vite 老项目 + 复杂构建 → 保持 Webpack 工具库 → Rollup(更纯净) Q14: ESLint + Prettier + Husky + lint-staged + Commitlint 完整工具链怎么搭? 📖 参考答案 / 解析分工: ESLint:代码质量(未使用变量、潜在 bug) Prettier:代码格式(缩进、引号、分号) Husky:Git Hooks 触发器 lint-staged:只检查 staged 文件,避免全量扫描 Commitlint:提交信息规范(Conventional Commits) 搭建步骤: # 1. 安装 pnpm add -D eslint prettier eslint-config-prettier eslint-plugin-prettier \ husky lint-staged @commitlint/cli @commitlint/config-conventional // package.json { "scripts": { "prepare": "husky install" }, "lint-staged": { "*.{ts,tsx,vue}": ["eslint --fix", "prettier --write"], "*.{json,md}": ["prettier --write"] } } # 2. 初始化 husky npx husky install npx husky add .husky/pre-commit "npx lint-staged" npx husky add .husky/commit-msg "npx --no -- commitlint --edit ${1}" // commitlint.config.cjs module.exports = { extends: ['@commitlint/config-conventional'] } 冲突处理: ESLint 与 Prettier 规则冲突 → eslint-config-prettier 关闭冲突规则 性能:大项目 lint 慢 → 用 --cache 选项 五、跨端开发 Q15: uni-app 和 Taro 怎么选?各自的局限是什么? 📖 参考答案 / 解析uni-app: 基于 Vue 编译目标多:H5 / 小程序(微信、支付宝、抖音、QQ、百度、京东等)/ App(基于 weex 或 Vue+原生混合) 国内生态强,文档全,社区活跃 DCloud HBuilderX 体验好 局限:App 端基于 weex 有性能瓶颈,复杂动画不流畅;TypeScript 支持后期才完善 Taro 3+: 基于 React(也支持 Vue 3) 运行时方案:把 React 组件树转成小程序的视图层 TypeScript 支持原生友好 React 生态可以直接复用(react-query、antd-mobile 等) 局限:性能略低于 uni-app(运行时方案有开销);国内社区不如 uni-app 火 选择标准: 团队 Vue 栈 + 主攻小程序 → uni-app 团队 React 栈 / TS 重度依赖 → Taro 需要原生体验的复杂 App → 都不太合适,建议 RN 或 Flutter 项目实战(针对简历): 医科大学过敏性项目用 Taro + React(团队 React 栈) 云上托育用 uni-app + Vue3(团队 Vue 栈) 商米 ERP 用 uni-app + Vue3(商米设备兼容性好) Q16: Electron 主进程 / 渲染进程怎么通信?安全风险有哪些? 📖 参考答案 / 解析通信方式: IPC(推荐): 渲染 → 主:ipcRenderer.send / ipcRenderer.invoke 主 → 渲染:ipcMain.on / webContents.send invoke 配合 handle 返回 Promise,比 send 更现代 contextBridge(安全做法): // preload.js contextBridge.exposeInMainWorld('electronAPI', { readFile: (path) => ipcRenderer.invoke('read-file', path), }) remote 模块(已废弃,不要用) 安全风险: nodeIntegration: true + 加载远程 URL → XSS 直接拿到 Node 权限,可执行任意命令 contextIsolation: false → preload 与 web 共享上下文,攻击者可篡改 API 未验证 IPC 参数 → 渲染进程构造恶意路径读取系统文件 最佳实践: nodeIntegration: false contextIsolation: true 用 contextBridge 暴露白名单 API IPC 参数严格校验 加载的 URL 校验来源 实战(简历的称重系统): 主进程持有 SerialPort 实例,处理底层串口通信 渲染进程通过 invoke 请求称重数据 preload 桥接 electronAPI.weigh() 六、可视化 / GIS Q17: 简历提到「100W+ 格点数据秒级渲染」,技术方案是怎样的? 📖 参考答案 / 解析总体思路:CPU 计算 → WebWorker,渲染 → Canvas 分块 + 离屏 + 抽帧 具体方案: 数据传输优化: 服务端用二进制(Float32Array / Protobuf)而非 JSON pako gzip 解压前端做(节省带宽 70%+) jszip 处理打包文件 计算分离: 主线程:DOM + 用户交互 WebWorker:插值、网格化、色斑映射计算 transferable 对象转移所有权,避免拷贝 渲染优化: Canvas 分块(tile):把大画布切成 256×256 的小块 离屏 Canvas (OffscreenCanvas) 预渲染 requestAnimationFrame 调度 只重绘视口可见区域 视觉欺骗: 大数据先渲染降采样版(10W 点)让用户看到东西 后台加载完整精度,无感切换 抗锯齿与色彩: chroma-js 做颜色插值 双线性 / 三次样条插值算法 性能指标(要能说清): 100W 点首次渲染 < 3s 平移 / 缩放保持 30+ FPS 内存占用 < 200MB 陷阱: WebWorker 与主线程通信序列化有开销,频繁通信反而慢 Canvas 太大(> 16384×16384)部分浏览器崩溃 Q18: WebWorker 使用边界?什么时候不该用? 📖 参考答案 / 解析适合用 WebWorker: CPU 密集型计算:加密、压缩、图像处理、地理网格化 大数据处理:> 1W 条数据的解析、排序、过滤 算法密集:路径规划、物理模拟、机器学习推理 不适合 / 用了反而慢的场景: DOM 操作:Worker 中没有 document / window 小数据频繁通信:序列化 / 反序列化开销可能比直接计算还大 简单同步任务:< 50ms 的任务不值得 Worker 通信成本: 默认是结构化克隆(深拷贝),大对象拷贝慢 Transferable 对象(ArrayBuffer / MessagePort / ImageBitmap)转移所有权,零拷贝 实战陷阱: Worker 文件路径在 Vite / Webpack 中需要特殊处理(new Worker(new URL('./worker.ts', import.meta.url))) 错误处理用 worker.onerror,错误信息可能被脱敏 调试用 Chrome DevTools 的 Sources → Workers 面板 Q19: Cesium 三维地形渲染原理?大数据场景怎么优化? 📖 参考答案 / 解析核心原理: 瓦片金字塔:地球被切成多个 LOD(细节层级),近处用高精度瓦片,远处用低精度 四叉树调度:相机移动时,根据距离与角度计算需要加载哪些瓦片 请求合并:瓦片以 HTTP 请求异步加载,浏览器并发 6 个连接 WebGL 渲染:地形 + 影像 + 矢量 + 模型层叠渲染 简历项目(航空气象)的优化点: GeoServer 提供 WMTS 高程瓦片:避免每次请求计算 缓存 + 预加载:常用区域提前预热瓦片缓存 降采样三维云图:远景用低分辨率纹理 裁剪策略:屏幕外瓦片不渲染(视锥裁剪由 Cesium 内置) 请求节流:相机快速移动时只请求最终位置的瓦片 踩坑: Cesium 包体积大(5MB+),需要按需引入或 CDN 内存泄漏:自定义 Primitive 要手动 destroy() iOS / 低端机性能堪忧 Q20: 风场流线动画的实现思路? 📖 参考答案 / 解析算法核心: 数据准备:风场数据是格点上的 U / V 分量(U=东西分量,V=南北分量),组成向量场 粒子初始化:在画布上随机散布 N 个粒子(典型 N = 5000~10000) 每帧更新: 对每个粒子,根据位置在风场中插值(双线性)出当前位置的风速 (u, v) 粒子位置加上 (u * dt, v * dt) 粒子寿命 -1,到 0 重生 渲染: 不要 clear 整个 canvas,而是用半透明矩形 globalAlpha=0.95 覆盖 这样粒子轨迹有"拖尾"效果 用粒子速度大小映射颜色(chroma-js) 性能优化: WebWorker 计算粒子位置,主线程只画 Canvas 用 GPU 加速:willReadFrequently: false 粒子数量自适应:根据 FPS 动态调整 进阶:可以用 WebGL 着色器在 GPU 上跑,性能 10 倍以上提升(参考 Mapbox 的 windgl 实现)。 七、游戏开发 Q21: Phaser.js 游戏循环(Game Loop)原理?怎么和 Vue 3 集成? 📖 参考答案 / 解析游戏循环原理: Phaser 内部用 requestAnimationFrame 维护一个主循环 每帧调用 Scene.update(time, delta): time:累计时间 delta:与上一帧的时间差(ms) 物理引擎 / 动画 / 输入都基于 delta 计算,保证不同帧率下行为一致 与 Vue3 集成方式: // PhaserGame.vue <template><div ref="container"></div></template> <script setup lang="ts"> import Phaser from 'phaser' import { ref, onMounted, onBeforeUnmount, markRaw } from 'vue' const container = ref<HTMLDivElement>() let game: Phaser.Game onMounted(() => { game = markRaw( new Phaser.Game({ parent: container.value, type: Phaser.AUTO, scene: [BootScene, MainScene], }), ) }) onBeforeUnmount(() => game?.destroy(true)) </script> 关键点: markRaw:Phaser 内部状态非常多,不要被 Vue 响应式劫持,否则性能崩溃 Vue 与 Phaser 通信:通过 EventBus 或 Phaser 的 EventEmitter HMR:开发时 destroy + recreate,否则会有多个实例 性能陷阱: 大量 sprite 用 Container 批量管理 文本对象慢 → 用 BitmapText 代替 帧率掉 → 用 Phaser DevTools 看哪个 system 占时间 Q22: 精灵图(Sprite Sheet)合并工具的实现思路? 📖 参考答案 / 解析核心步骤(针对简历提到的自研工具): 扫描资源:读取目录下所有 PNG 打包算法:用 MaxRects / Skyline / Guillotine 算法把多个小图塞进大图 推荐用现成库 maxrects-packer 生成图集:把所有小图绘制到一张大 Canvas 生成元数据:JSON 描述每个 sprite 在大图中的 (x, y, w, h) 输出:PNG + JSON(Phaser / Pixi 都能直接消费) 优化点: 旋转:把竖图旋 90° 节省空间 Padding:小图之间留 1-2px,避免渲染时采样到邻居 多张图集:超过 2048×2048 拆分(兼容老 GPU) TinyPNG / pngquant 压缩 业务价值: 减少 HTTP 请求数(10 张图 → 1 张) GPU 纹理切换次数减少(同图集的 sprite 不切换 batch) 节省内存(合并后总体积小) 八、硬件集成 Q23: SerialPort 串口通信的关键流程?有哪些坑? 📖 参考答案 / 解析流程: import { SerialPort } from 'serialport' import { ReadlineParser } from '@serialport/parser-readline' const port = new SerialPort({ path: 'COM3', baudRate: 9600, dataBits: 8, parity: 'none', stopBits: 1, }) const parser = port.pipe(new ReadlineParser({ delimiter: '\r\n' })) parser.on('data', (data) => { console.log('Recv:', data) }) port.write('READ\n', (err) => { if (err) console.error(err) }) 关键点: 波特率必须和设备一致(9600 / 19200 / 115200 是常见值) 数据帧用 Parser:原始流是字节流,要按协议分帧(换行符 / 字节长度 / 自定义分隔符) 设备路径:Windows 是 COM3,Mac/Linux 是 /dev/tty.usbserial-xxx 坑: 设备不存在 / 占用:要 catch Error: Opening COM3: Access denied 断线重连:监听 close 事件,定时器重试 open 字符编码:电子磅秤常用 GB2312,需要 iconv-lite 转码 数据丢包:Buffer 满了不及时读取 多设备识别:用 USB VID/PID 而不是 COM 号(COM 号会变) 进阶:Web Serial API 在 Chrome 89+ 支持,无需 Node 也能调串口(但兼容性差,只能用 Chromium 内核)。 Q24: Koffi(C 动态库调用)你怎么用的?相比 ffi-napi 优势在哪? 📖 参考答案 / 解析Koffi 简介:Node.js 调用 C/C++ 动态库(.dll / .so / .dylib)的工具,是 ffi-napi 的现代替代。 基本用法: import koffi from 'koffi' const lib = koffi.load('myhardware.dll') // 声明函数原型 const readCard = lib.func('int ReadCard(char *buffer, int len)') const buffer = Buffer.alloc(256) const result = readCard(buffer, buffer.length) console.log(buffer.toString('utf8')) 相比 ffi-napi 优势: Node.js 版本兼容性好:ffi-napi 在 Node 18+ 经常编译失败,Koffi 用纯 JS + N-API,无需编译 Electron 友好:electron-rebuild 噩梦没了 性能更好:Koffi 自带内联汇编优化 API 更现代:类型声明用 C 语言字符串,更直观 维护活跃:ffi-napi 已基本不更新 简历项目(屠宰场 ERP)应用: 调用厂商提供的 IC 卡读写 SDK(C dll) 调用身份证识别 SDK 二维码扫码模块 SDK 踩坑: 32 位 vs 64 位 dll 要匹配 Node 架构 字符串编码(很多国产 dll 用 GBK) 回调函数:Koffi 支持 callback 注册,但生命周期要小心 九、实时通信 Q25: WebSocket 心跳保活与断线重连怎么设计? 📖 参考答案 / 解析心跳保活: class WSClient { private ws: WebSocket private heartbeatTimer: any private reconnectTimer: any private reconnectCount = 0 connect() { this.ws = new WebSocket(this.url) this.ws.onopen = () => { this.reconnectCount = 0 this.startHeartbeat() } this.ws.onmessage = (e) => this.handleMessage(e) this.ws.onclose = () => this.reconnect() this.ws.onerror = () => this.ws.close() } private startHeartbeat() { this.heartbeatTimer = setInterval(() => { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type: 'ping' })) } }, 30_000) // 30s } private reconnect() { clearInterval(this.heartbeatTimer) if (this.reconnectCount >= 5) return // 放弃 const delay = Math.min(1000 * 2 ** this.reconnectCount, 30_000) // 指数退避 this.reconnectTimer = setTimeout(() => { this.reconnectCount++ this.connect() }, delay) } } 关键设计点: 心跳间隔:30s 是常见值,太短浪费带宽,太长可能被中间代理踢 指数退避:1s → 2s → 4s → 8s → 16s,避免雪崩 重连上限:5-10 次后放弃,避免无限重试 应用层 ACK:服务端收到 ping 要回 pong,超时未收到说明链路坏了 状态机:connecting / connected / disconnected / reconnecting,避免重复连接 消息队列:断开期间的消息缓存,重连后批量发送 实战陷阱: 网络切换(WiFi → 4G)时 onclose 可能不触发,需要 setTimeout 主动检测 浏览器后台标签页定时器被限流,心跳可能漏发 移动端进入后台时主动关闭连接,避免被系统杀 Q26: MQTT 和 WebSocket 区别?什么时候用 MQTT? 📖 参考答案 / 解析协议层面: WebSocket:基于 TCP,是一个通用的全双工通道,没有规定上层语义 MQTT:基于 TCP(或 WebSocket),是一个发布/订阅协议,专为 IoT 设计 核心差异: 特性WebSocketMQTT通信模式点对点发布订阅(Topic)消息保证无内置QoS 0/1/2 三档离线消息无持久 Session数据格式无规定二进制头部,紧凑适用场景Web 实时交互IoT、传感器、物联网MQTT 适合用的场景(针对简历的云上托育项目): 多对多通信:温度传感器发布到 dorm/temperature,所有教师端订阅 离线消息:传感器掉线后再上线,收到积压消息 海量设备:MQTT broker 能扛百万级连接 低带宽:协议头部小(最小 2 字节) 浏览器端:用 mqtt.js,底层走 WebSocket(因为浏览器不能开 raw TCP) 踩坑: Topic 设计:用 / 分层,支持通配符 + 和 # QoS 1 / 2 是端到端的,broker 不保证业务层处理成功 公网 broker 要加 TLS(mqtts://) Q27: Agora 实时音视频集成关键点?延迟怎么优化? 📖 参考答案 / 解析集成关键点: Token 鉴权:服务端用 App Certificate 签发临时 Token,前端不能硬编码 App ID 的明文 频道(Channel):所有进入同一频道的用户互通 角色:主播(host) vs 观众(audience),权限不同 轨道(Track): LocalAudioTrack / LocalVideoTrack:本地采集 RemoteAudioTrack / RemoteVideoTrack:订阅远端 典型流程: const client = AgoraRTC.createClient({ mode: 'rtc', codec: 'vp8' }) await client.join(appId, channel, token, uid) const localAudio = await AgoraRTC.createMicrophoneAudioTrack() const localVideo = await AgoraRTC.createCameraVideoTrack() await client.publish([localAudio, localVideo]) client.on('user-published', async (user, mediaType) => { await client.subscribe(user, mediaType) if (mediaType === 'video') user.videoTrack.play(`remote-${user.uid}`) }) 延迟优化: codec 选择:H.264 兼容性好,VP8/VP9 压缩率高,H.265 最优但兼容性差 分辨率自适应:弱网降到 480p 带宽探测:Agora SDK 内置,自动调整 接入点就近:Agora 全球节点,主播 / 观众都连最近的边缘节点 抗丢包:FEC 前向纠错 + ARQ 重传,移动端必开 针对简历游戏直播场景: 主播端用游戏画面采集(createScreenVideoTrack)+ 麦克风 观众端只订阅,不发布 互动用 RTM(实时消息)补充,做礼物 / 弹幕 十、浏览器 / 性能 Q28: 浏览器渲染流程?哪些 CSS 操作会触发 Reflow? 📖 参考答案 / 解析渲染流程: HTML → DOM Tree CSS → CSSOM Tree ↓ Render Tree (合并) ↓ Layout (布局/回流 Reflow) ↓ Paint (绘制/重绘 Repaint) ↓ Composite (合成图层) 触发 Reflow 的操作: 修改 width / height / padding / margin / border 修改 position / top / left 修改 font-size / font-weight 读取 offsetWidth / scrollTop / getComputedStyle() 等(强制同步布局) 增删 DOM 节点 修改 class 影响布局 只触发 Repaint(不 Reflow): 修改 color / background-color / visibility 只触发 Composite(不 Repaint): transform / opacity(前提是元素已提升为合成层) 优化建议: 批量修改:document.createDocumentFragment() 或 display: none 后修改 避免强制同步布局:读写分离(先读 → 一次性写) 用 transform 替代 left/top:动画专用 will-change 提示:提前提升合成层(注意不要滥用) CSS Containment:contain: layout/paint/strict 面试常考: 强制同步布局是什么?读 offsetWidth 时浏览器必须先完成 pending 的布局 合成层提升的代价是什么?内存(每层都是独立位图) Q29: JS 事件循环?宏任务 / 微任务有哪些? 📖 参考答案 / 解析事件循环模型: ┌──────────────────────────┐ │ 执行同步代码(Call Stack) │ └──────────────────────────┘ ↓ ┌──────────────────────────┐ │ 执行所有微任务(清空队列) │ └──────────────────────────┘ ↓ ┌──────────────────────────┐ │ 渲染(如果需要) │ └──────────────────────────┘ ↓ ┌──────────────────────────┐ │ 执行一个宏任务 │ └──────────────────────────┘ ↓ 循环 宏任务(每次循环执行一个): setTimeout / setInterval setImmediate(Node 独有) I/O(文件、网络) UI 事件(click、scroll) MessageChannel 微任务(一次循环清空全部): Promise.then / catch / finally queueMicrotask MutationObserver process.nextTick(Node,优先级比 Promise 还高) 经典面试题: console.log('1') setTimeout(() => console.log('2')) Promise.resolve().then(() => console.log('3')) console.log('4') // 输出:1 4 3 2 进阶: Node.js 事件循环分 6 个阶段(timer / pending / idle / poll / check / close),与浏览器不完全一样 await 等价于 Promise.then,后续代码进微任务队列 浏览器渲染时机:每次微任务清空后,但具体时机取决于浏览器(不一定每帧都渲) Q30: 内存泄漏常见场景与排查工具? 📖 参考答案 / 解析常见场景: 未清理的定时器: setInterval(() => { /* ... */ }, 1000) // 组件卸载时未 clearInterval 未解绑的事件监听: window.addEventListener('resize', handler) // 组件卸载时未 removeEventListener 闭包持有大对象: function createHandler() { const bigData = new Array(1e6) return () => console.log(bigData.length) // bigData 永不释放 } DOM 引用: const cache = {} cache.el = document.getElementById('foo') document.body.removeChild(cache.el) // cache.el 仍持有 DOM WebSocket / WebWorker 没关 Vue / React 组件中 ref 第三方实例(Phaser / Cesium)未 destroy 全局变量越来越大(缓存策略不当) 排查工具: Chrome DevTools Memory 面板: Heap Snapshot:拍快照,对比两次操作前后的对象数量 Allocation timeline:录制内存分配过程,找出热点 Allocation sampling:低开销采样,长期监控 Performance 面板:JS Heap 曲线,看是否单调递增(典型泄漏) Memory tab 中的 Retainers:看对象被谁引用了,找到泄漏源头 实战流程: 复现操作(如反复打开关闭弹窗) 拍 baseline Heap Snapshot 操作 10 次 再拍一次 Heap Snapshot,Comparison 模式 找 Delta 异常增长的对象(如 Detached HTMLDivElement) 看 Retainers 链路,找到根因 Q31: 首屏性能优化你做过哪些?怎么衡量? 📖 参考答案 / 解析关键指标(Core Web Vitals): LCP(Largest Contentful Paint):最大内容绘制,反映首屏体感,目标 < 2.5s FID / INP(Interaction to Next Paint):首次输入延迟 / 交互响应,目标 < 200ms CLS(Cumulative Layout Shift):累计布局偏移,目标 < 0.1 FCP(First Contentful Paint):首次内容绘制 TTFB(Time to First Byte):服务器响应时间 优化手段(按收益排序): 资源加载: CDN 加速 HTTP/2 多路复用 关键资源 preload,非关键 prefetch 字体子集化(fonttools) 图片:WebP / AVIF + 响应式 srcset 代码分割: 路由级懒加载 第三方库按需引入 抽离 vendor chunk 服务端: SSR / SSG 边缘渲染(Vercel Edge / Cloudflare Workers) 接口聚合,减少串行请求 运行时: 骨架屏代替白屏 渐进式加载(先低质量图,再替换高清) 关键 CSS 内联 defer / async 控制脚本执行时机 缓存: HTTP 强缓存(不变资源)+ 协商缓存(HTML) Service Worker 离线缓存 衡量工具: Lighthouse(命令行 + Chrome 面板) WebPageTest(多地区测试) PerformanceObserver API(线上 RUM 上报) Chrome DevTools Performance Insights 实战案例(简历的星光华人通项目): 公众号网页首屏 LCP 优化:骨架屏 + 图片懒加载 + 接口缓存 部署到 CDN 关键 CSS 内联 十一、AI 编码助手 Q32: Claude Code / Copilot / Cursor 你都怎么用?哪些场景不该用? 📖 参考答案 / 解析实际使用场景(按效果排序): 模板代码生成:CRUD 表单、表格、列表页 → 节省 60%+ 时间 类型声明:从 JSON 生成 TypeScript interface 测试用例:从函数签名生成 Vitest 用例 重构辅助:把 Options API 转 Composition API、提取 hook 文档生成:README、JSDoc、变更日志 复杂正则:自然语言描述 → 正则表达式 代码 Review:让 AI 先看一遍,找明显问题 算法实现:经典算法直接生成 配置文件:Webpack、Vite、ESLint 配置 不该用 / 慎用的场景: 业务核心逻辑:AI 不懂上下文,容易写出"看起来对"的代码 安全敏感:鉴权、加密、SQL 拼接 → AI 可能引入漏洞 性能关键路径:AI 倾向于"通用解",性能不一定最优 数据库迁移 / 删除操作:风险太大,必须人工 review 不熟悉的领域:AI 可能编造(hallucination)API / 库 公司机密代码:要选择不上传训练的工具(Claude Code 是较好选择) Claude Code 独特价值(vs Copilot / Cursor): 多文件 agent 能力强,能跨文件理解项目 长上下文,可以读完整文档 工具调用(执行 bash、读写文件)— 适合自动化任务 心得:AI 是"加速器"不是"代驾"。让它写,然后逐行 review。 十二、项目深挖 Q33: Spinman 平台 20+ 游戏怎么管理?新游戏怎么接入? 📖 参考答案 / 解析架构示意: spinman-monorepo/ ├── packages/ │ ├── shared/ # 共享 UI 组件、工具函数、类型 │ ├── core/ # 游戏引擎封装(Phaser / Pixi) │ ├── auth/ # 鉴权 SDK │ ├── socket/ # WebSocket / Agora 封装 │ └── live/ # 直播 UI 模块 ├── games/ │ ├── game-slot/ # 游戏 1 │ ├── game-dice/ # 游戏 2 │ ├── game-poker/ # 游戏 3 │ └── ... (20+) ├── shell/ # 主应用壳子(路由、布局、鉴权入口) └── tools/ └── create-game/ # 新游戏脚手架 新游戏接入流程: pnpm create-game game-newxxx → 脚手架生成模板 自动注入到 shell 的路由表(动态扫描 games/*) 共享 UI / 鉴权 / 通信,开发者只关注游戏本身逻辑 CI 自动按依赖图增量构建(Turborepo) 发布:每个游戏独立构建产物 + CDN 部署 核心收益: 新游戏从 0 到能跑:~30 分钟 公共 bug 修一处所有游戏受益 多游戏可同时迭代不冲突 踩坑: 共享库版本:用 workspace:* 内部依赖 Vite 多入口构建:要配置 optimizeDeps.entries 类型共享:tsconfig 用 path 映射,编辑器支持好 Q34: 灵创你带 3 人团队,怎么排期 / 协作 / Review? 📖 参考答案 / 解析排期方式: 需求评估会:产品讲需求 → 技术分解为 task → 估时(1d / 3d / 5d 三档) 进 Jira / 飞书项目,每周冲刺 5d 每日站会 15min:昨天做了什么、今天做什么、有什么 blocker 协作机制: Git Flow:main / develop / feature/* / hotfix/* 强制 PR Review:至少 1 人 approve 才能合并 PR 描述模板:变更原因、影响范围、测试方式、截图 Conventional Commits + 自动生成 CHANGELOG Code Review 重点: 命名是否清晰 是否有重复代码(参考已有 utils) 类型是否完整 边界 case(空数组、null、网络错误) 性能(循环里调函数、大对象 watch) 安全(XSS、SQL 注入、敏感信息硬编码) 培养机制: 每周技术分享(30min),轮流主讲 推荐高质量文章 / 视频,建知识库 复杂任务结对编程 季度做技术总结,输出文档沉淀 实际产出(针对简历): 工程化规范从 0 到 1(ESLint + CI/CD + 自动打包) 线上 Bug 率降低(事故复盘 + 单测要求) 团队能独立 take 跨行业项目 Q35: 100W+ 格点数据"秒级展示",秒级具体多少秒?瓶颈在哪? 📖 参考答案 / 解析真实指标(要能讲清): 数据传输:< 1s(gzip 压缩后 ~3MB) WebWorker 计算插值 / 着色:~0.5s Canvas 渲染:~0.3s 总计:< 3s 首屏可见 主要瓶颈(按优化顺序): 网络传输(瓶颈 1): 原始 JSON 30MB+ → 用 Float32Array 二进制 + pako gzip → 3MB HTTP/2 多路复用 JS 计算(瓶颈 2): 主线程做计算会卡死 → WebWorker 异步 算法选择:双线性插值 vs 三次样条 → 双线性够用且快 Canvas 绘制(瓶颈 3): 单个 fillRect 100W 次太慢 → 用 ImageData 一次性写入 ctx.putImageData() 是 GPU 加速的 DOM 阻塞(瓶颈 4): 大批操作放在 requestIdleCallback / requestAnimationFrame 避免长任务(> 50ms) 剩余优化方向: WebGL Shader 直接渲染(10 倍性能,但开发复杂度高) WASM 计算(适合更复杂的算法) WebGPU(未来方向,浏览器支持中) 坦诚说明:3 秒不算极致,对气象决策场景已够用。如果是高频交互(如游戏),还需进一步优化到 < 1s。 Q36: 三家托育机构 50% 复用率怎么测算?怎么实现的? 📖 参考答案 / 解析复用率测算(粗略口径): 总代码量:约 30K 行(园长端 + 教师端 + 家长端) 共享代码:约 15K 行(业务逻辑、UI 组件、工具函数) 复用率 = 15K / 30K = 50% 实现方式: miniapp-monorepo/ ├── packages/ │ ├── shared/ │ │ ├── components/ # 通用 UI(按钮、表单、列表) │ │ ├── hooks/ # 通用 composables │ │ ├── utils/ # 工具函数 │ │ ├── api/ # 接口封装 │ │ └── types/ # 类型定义 │ ├── biz-shared/ │ │ ├── auth/ # 鉴权(不同角色共用) │ │ ├── upload/ # 文件上传 │ │ └── canvas-draw/ # 跨平台 Canvas 绘制(课程表、海报) ├── apps/ │ ├── principal/ # 园长端 │ ├── teacher/ # 教师端 │ └── parent/ # 家长端 关键决策: 角色相关逻辑放在各 app(不强行共享) 跨角色一致的业务抽到 biz-shared(如鉴权、上传) 纯 UI / 工具全放 shared 用 pnpm workspace 软链接,改一处所有端实时生效 带来的收益: 修 bug 一处生效 三端 UI 风格统一 新增功能(如海报分享)一次开发三端可用 踩坑: 小程序对 node_modules 引用方式不一样,要配 subPackages 不同角色权限差异:用 provide / inject 注入角色信息,组件按角色裁剪显示 十三、软实力 / 团队管理 Q37: 你怎么做技术选型?举一个具体决策的例子。 📖 参考答案 / 解析我的方法论: 明确约束:业务场景、团队栈、时间预算、未来扩展 列候选:通常 2-4 个,避免只有一个"信仰选择" 多维评估: 学习曲线 社区活跃度(npm 周下载、GitHub issue 响应) 生态完整度 性能 benchmark 文档质量 长期维护(避免选个人项目) 公司投入(背靠大厂的更稳) POC 验证:用 1-2 天写小 demo,验证核心难点 决策记录:写 ADR(Architecture Decision Record),未来 review 具体例子:跨端框架选 uni-app vs Taro 约束: 团队 Vue 栈 目标:微信 + 抖音 + 支付宝小程序 + H5 时间:1.5 个月上线 评估: uni-app:Vue 生态、文档全、HBuilderX 友好 Taro:React 栈(团队不熟)、TS 友好但生态略弱 决策:选 uni-app,理由: 团队上手快 多端支持成熟 DCloud 商业支持靠谱 结果:1.5 个月按时上线,多端体验一致。 反思:如果是 5 年规划的项目 + React 团队,Taro + TS 可能更适合。 Q38: 跨部门 / 客户协作中遇到的最大挑战是什么? 📖 参考答案 / 解析典型场景(针对简历"作为技术代表对接合作伙伴和客户"): 最大挑战:客户对技术不懂,但有非常具体的"业务直觉",两者经常冲突。 举例: 客户:"这个页面再快一点" → 实际首屏已经 1.5s 客户:"数据要实时" → 实际场景 10s 延迟也可接受 客户:"手机也要看" → 实际后台系统在 PC 端用得多 应对方式: 翻译需求:把客户的"快"翻译为"FCP < 1s + 首屏 < 2s",给具体指标 数据说话:埋点统计实际场景,用数据反驳"直觉"(如:实时改 10s 轮询,看用户感知差异) AB 测试:争议时上线两版本对比 优先级管理:建 backlog,公开排期,避免临时插需求 教育客户:偶尔讲讲技术原理,提升对方理解 关键心态: 不要把客户当"对立面",他们的需求往往有真实业务痛点,只是表达不准 技术人多问几个"为什么",往往能挖到本质需求 不要用技术术语压人,用对方能理解的话沟通 反例(不该做的):直接说"这不可能" / "我们做不了" → 应该说"做这件事的成本是 X,如果调整成 Y 方案,成本能降到 Z"。 Q39: 你对自己未来 3-5 年的规划是什么? 📖 参考答案 / 解析这题答案要"真诚"+"匹配岗位",给个框架: 短期(1 年): 在新公司站稳,深度参与至少 1 个核心项目,沉淀有影响力的技术产出 补齐当前的短板(如:大规模 C 端经验、性能监控体系、SSR 深度) 持续输出博客 / npm 工具 中期(2-3 年): 在某个细分方向(前端基建 / 可视化 / 跨端)成为团队公认的"专家" 主导 1-2 个具有公司级影响力的技术项目(如基建升级、性能优化专项) 参与开源社区,至少 1 个项目 1k+ star 长期(3-5 年): 技术专家路线:前端架构师 / Principal Engineer,主导技术方向 或:技术管理路线:带 10+ 人团队,负责业务线技术体系建设 持续保持动手能力,不要变成"PPT 工程师" 核心目标:在 35 岁前,建立独特的技术影响力(开源 / 文章 / 演讲 / 公司内 case),而不是只有"工作年限"。 实诚版:现阶段最关注的是找一个能让我深度成长的平台。当前已经做过广度,下一步想专精深度。 避免的回答: "我想成为 CTO" → 太大,没法验证 "随便,看公司安排" → 缺乏主动性 "想做管理" / "想做技术" 二选一 → 过于绝对,应该是双向开放 学习建议 每天 1 题深挖 不要追求"题目数量",重点是每题都能用自己的话讲 5 分钟,能举例、能反驳、能扩展。 知识体系图 把所有题目按"技术栈 → 子领域 → 具体知识点"画成思维导图,找出自己的薄弱区。 模拟面试 找朋友 / 同行模拟 1-2 次,重点训练: 表达清晰度(不能磕巴) 引导节奏(不能被追问到死胡同) "我不知道" 的优雅回答 项目复盘准备 简历每个项目都准备 3 个深挖问题 + 1 个失败案例(HR 喜欢问挫折)。 祝面试顺利 🚀
← 返回简历