Skip to content

面试问题清单 · 逐题回答草稿(含仓库代码实证)

使用说明

  • 每道题给出:问题原文涉及文件/代码位置(已核对过,可回去确认)→ STAR 口语化回答(按 30–60 秒压口述版)→ ⚠️ 无依据 / 需要想清楚的说法
  • 带 ⚠️ 的条目表示:代码里没找到能支撑这个说法的实现或测量依据,面试前务必自己补一段话术,否则追问会露馅。
  • 所有路径均相对各自仓库根目录。口语化回答仅供你练习,别背稿,讲你真实做过的部分。

〇、仓库 → 问题清单映射表

问题对应仓库核心文件
1–4, 15–24workspace/electron-detectorpackages/electron-app/src/main/**
5–9通用 Vue 知识 + emi-main-app(Pinia store)、electron-detector(Composition API)见各题
10–14workspace/emi-main-app(Wujie 微前端)src/components/ReWujieSubApp/**src/layout/components/appMain.vue
15–19workspace/electron-detectorpackages/electron-app/src/main/index.tsservices/**
20–24workspace/electron-detector + emi-andon-system(Web Serial)services/iot-gateway.service.tsudp.service.ts
25–26workspace/emi-digital-twinssrc/hooks/useThree/**
27electron-detector(ECharts 实时图)、nuxt-big-screen(大屏)src/renderer/src/views/home/components/gague.vue
28workspace/nuxt-website(中恒智造官网)nuxt.config.jspackage.json
29emi-main-app(Vite)、nuxt-website(Webpack)、fund-helper-vscode(esbuild)vite.config.tsnuxt.config.js
30— (无量化依据)
31–32workspace/zhihu-fisher-vscodesrc/core/zhihu/puppeteer/index.tssidebar/recommend.tsutils/code-generator.ts
33zhihu-fisher-vscodefund-helper-vscodeREADME.mdCHANGELOG.md
34workspace/fund-helper-vscode/webcf.jsnetlify/functions/proxy.tsvite.config.tssrc/fundService.ts
35–38软技能(无代码)

补充:workpace 里还有 huidong-cmcc-*(惠东小程序 + Express/Prisma/MySQL 后端)、mobile-smzdm-botzh-keyboard(触屏键盘)、Kiro-auto-register 等,主要支撑「全栈/后端」定位,未在清单中单独出题,但可在 15/34 时顺带提后端功底。


一、开场 & 项目真实性核验

1. 请用 3 分钟介绍最有代表性的「智能回潮率检测仪」,重点讲个人负责的部分。

涉及文件electron-detector/packages/electron-app/src/main/**prisma/schema.prismaREADME.md

STAR 30–60s 版

「背景:纺织厂测回潮率靠人工 + 离线仪表,数据不落库、和产线设备对不上。任务:我独立架构并开发了这个 Electron 上位机,把『设备通信→数据采集→公式计算→检测记录→报告导出→云端同步』一条链做完。行动:主进程用单例锁 + 开机自启 + 全屏工业模式,Preload 桥接 IPC;内置 Aedes MQTT Broker 接物联网关,UDP 100ms 轮询探头 Q/M 值;Prisma + SQLite 离线落库;electron-updater 自动更新;WebSocket 心跳 + 60s 测量超时复位 + 未捕获异常自动重启来兜底。结果:已部署到产线,支持多次测量、品种/批次管理、报表打印。」

方式提醒:这是你最能讲的题,但别把所有东西都倒出来,留钩子给后面的追问(IPC 怎么选、Prisma 打包怎么排雷、UDP 丢包怎么办)。

2. ⭐ 「独立架构与开发」,团队几个人?和其他角色分工边界?

涉及文件:仓库 git log、packages/ 多包结构(electron-app + backend + admin-web + mock + thread-detector)。

STAR 版

「这个项目基本是我一个人从 0 到 1 端到端做的,前端、Electron 主进程、内嵌后端、数据库模型都是我来。硬件/嵌入式那一侧是硬件工程师负责下位机和协议定义,我把 FL-WAPP 电报协议、物联网关 MQTT 收发这些对接逻辑写在 udp.service.tsiot-gateway.service.ts;测试这块主要靠现场联调和自测,云端设备管理那套接口我搭了 backend 包。」

⚠️ 要想清楚:简历写「独立架构与开发」,面试官大概率追问「那后端是谁写的」。诚实回答「后端也是我一人设计实现,跑在 Electron 主进程里的精简版」,同时说清楚硬件侧(PLC/物联网关)不是你的边界,避免被当成吹牛。

3. 从需求评审到上线,开发周期多久?最大一次返工是什么原因?

涉及文件electron-detector git log(提交集中在 2026-02/03 起步,持续到 08 月)。

STAR 版

「从设备协议梳理到第一版能上线大概几个月,后面一直在迭代。最大的返工是实时数据采集的架构:早期我用请求-响应模式一个一个问探头,现场发现时序错乱、偶尔无响应、还卡 UI。后来我重构成 持续 UDP 轮询,用一个 RealtimeDataCollectorService 定时 100ms 主动 push Q/M 值进环形缓存,配合滑动窗口 + 去极值取中值,彻底解决时序问题。这个 commit 在 git 里能翻到。」

证据src/main/api/services/realtime-data-collector.service.ts 顶部注释明确写「不再使用请求-响应模式,采用定时发送 UDP 请求持续收集」。

4. 「7×24 小时不间断作业」怎么验证/监控?有没有真实故障率数据?

涉及文件src/main/index.ts(uncaughtException → 1s 后 app.relaunch());services/heartbeat.service.tsservices/data-sync.service.tsservices/device-auth.service.ts

STAR 版

「没有单一监控看板,我是靠三层兜底硬扛的:① 全局 uncaughtException/unhandledRejection 只记日志不退出,严重错误 1 秒后 app.relaunch();② 测量 60 秒超时在 iot-gateway.service.ts 自动复位;③ 云端有设备注册 + 心跳保活(heartbeat.service.ts 断网时 60s 慢速重试,在线切 500ms 快心跳),再叠加 5 分钟一次的数据同步。相当于『出问题能自愈』。」

⚠️ 要想清楚:简历的「真实故障率数据」——代码里没有任何 uptime / 故障率 / MTBF 的统计或日志维度。面试官问「有没有真实数据」时,别硬编数字,老实说「当时主要靠现场盯和日志,没建成自动化故障率报表」,同时把上面的兜底机制讲足,反而显得真实。


二、Vue2/3 全家桶

5. Vue3 响应式原理 vs Vue2 核心区别?

涉及文件emi-main-app/src/store/**(Pinia/composition store)、electron-detector renderer store(Composition API)。

STAR 版(通用知识,无仓库实现)

「Vue2 用 Object.defineProperty 必须预先声明属性、对象新增/删除属性无感知,数组索引也监听不全;Vue3 用 Proxy 代理整个对象,读取 key 时依赖收集(track),写入时 trigger,运行时新增/删除都能被拦截,还顺带处理了 Map/Set。Side effect 层面 Vue3 用 WeakMap(target→Map(key→Set(effect))) 维护依赖,配合 ref/reactive/computedeffectScope。手写一个骨架我能写出来。」

⚠️:这题仓库里没有实现,纯靠源码理解。建议真的能手写一遍 20 行版 reactive(对应第 8 题),不要只背概念。

6. Composition API 解决的痛点?举一个你重构过的例子。

涉及文件electron-detector/packages/electron-app/src/main/api/services/realtime-data-collector.service.ts(重构证据)。

STAR 版

「Composition API 最大的用处是把『同一数据的逻辑』从选项里抽出来聚合。我最典型的例子是上面说的实时采集重构:原来和测量、MQTT 混在一起的逻辑,我抽成独立的 RealtimeDataCollectorService(组合式),它只负责 100ms 轮询 + 环形缓存 + 校验区间,IoTGatewayService 订阅它的 q-value-collected 事件取数,解耦后各自能独立单测、能复用到校准曲线等其他流程。」

渲染侧(前端)用 Composition API 的说明:electron-detector/src/renderer/src/stores/measurement.tsformula.ts 都是 Pinia setup store 写法,把配置、测量、公式三段逻辑拆开。

7. Pinia 和 Vuex 的本质变化?Store 怎么划分?

涉及文件emi-main-app/src/store/modules/**(user/epTheme/layout 等分模块);electron-detector/src/renderer/src/stores/**(measurement、formula)。

STAR 版

「Pinia 去掉了 mutations,state/getters/actions 扁平,天然 Composition API + TS 推断,没有命名空间嵌套。我在 emi-main-app 里按领域拆模块:user(角色/Token)、epTheme(主题色)、layout(布局)各一个 store,单文件即模块,比 Vuex 的 modules 配置清爽。」

8. ⭐ 能手写一个简化版 reactive?

涉及文件:无仓库实现(源码理解题)。

STAR 版(当场写)

ts
const targetMap = new WeakMap() // target -> Map(key -> Set(effect))
let activeEffect = null
function track(target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) targetMap.set(target, (depsMap = new Map()))
  let dep = depsMap.get(key)
  if (!dep) depsMap.set(key, (dep = new Set()))
  dep.add(activeEffect)
}
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  dep && [...dep].forEach(e => e())
}
function reactive(target) {
  return new Proxy(target, {
    get(t, key, r) { track(t, key); return Reflect.get(t, key, r) },
    set(t, key, val, r) { const ok = Reflect.set(t, key, val, r); trigger(t, key); return ok }
  })
}
function effect(fn) { activeEffect = fn; fn(); activeEffect = null }

对 shouldTrack / 嵌套 effect / 数组派发 这些再补两句即可。

⚠️:代码里没有,纯粹考你源码是否真研究过。上面这段背熟。

9. <script setup> 编译时优化?

涉及文件:无仓库实现(通用知识)。

STAR 版

「每个 <script setup> 组件会被编译成一个 setup() 函数,顶部自动 __defineProps/__defineEmitsconst/ref 声明提升为局部变量、编译产物更小;模板里的变量引用会被编进 setup 作用域,天然 TS 类型推导;同时也因为变量是局部闭包,模板编译期的静态提升(hoisting)和 cacheHandlers 也能更好地命中。」

⚠️:仓库无对应实现,纯知识。


三、微前端(Wujie)

10. ⭐ Wujie 核心隔离原理?

涉及文件emi-main-app/src/components/wujie/wujie-core/src/(vendored 源码:shadow.tsiframe.tsproxy.tssandbox.tstemplate.ts)。

STAR 版

「Wujie 是双层隔离:样式用 WebComponent(shadow DOM)给每个子应用开一个 shadow root,CSS 作用域天然隔离;JS 用 iframe 开一个全新 window,再用 Proxy 劫持子应用的全局读写,把局部变量/事件等代理归还子应用。为什么不能只用一个:只靠 shadow 管不住 JS 全局污染;只靠 iframe 又拿不到浏览器原生组件(弹窗、innerWidth 等)的顶层行为,所以两者各管一半叠起来才完整。」

我用 Wujie 时还写了不少 polyfill 插件(ReWujieSubApp/src/hooks/plugins.ts):修 innerWidth/innerHeight 不随主应用变(jsBeforeLoaders 里重写 getter,博客也发过),修 ElementPlus/popper 在无界下的定位、wangEditor 的报错等。

11. Wujie 和 qiankun 的本质区别?重新选型选谁?

涉及文件:上述 vendored wujie-core(对比用)。

STAR 版

「qiankun 用类似 single-spa 的思路,把子应用拉到主应用同上下文里跑,靠 import-html-entry 抓 HTML,再用 proxy 沙箱 + CSS 前缀/scoped 做隔离,首屏好但侵入大、和主应用 JS 全局是共用的;Wujie 用子应用放 iframe 沙箱 + 动态 replace HTML 进 shadow,保真度高一点、改造少,代价是每次 iframe 通信和进沙箱。真要我重新选:如果子应用大多是老项目、改造成本敏感,我选 Wujie;如果子应用都是新写的、团队要严格统一,qiankun 生态更主流。我们那个中台 app 就是基于 Wujie 还 vendored 了源码做深度插件定制。」

12. 「Wujie Bus 总线」怎么实现?有没有时序问题?

涉及文件emi-main-app/src/components/ReWujieSubApp/src/app.tsx(bus.$emit({name}RouteChange)、props 传 dataTheme/layoutTheme/userParams)。

STAR 版

「我用 WujieVue.bus 做事件总线,但给每个子应用绑了独立事件名${props.name}RouteChange),避免所有子应用抢同一事件导致一起跳转。主题/暗黑/权限这些不走 bus,直接通过 window.$wujie.propsdataThemelayoutThemeuserParams(role/token) 传下去,子应用订阅 props 变化。时序问题确实遇到过:子应用还没激活时发路由事件,控制台会报『事件订阅数量为空、事件丢失』——我在代码里留了 TODO,方案是主应用 store 里存个 flag,子应用激活后再补发一次,而不是事件一丢就完事。」

⚠️ 要想清楚:简历写「Wujie Bus 总线通信机制」,真实实现里 bus 只管路由跳转事件,主题/角色/Token 是靠 props 透传,不是全走 bus。别笼统说「都用 bus 同步」——把主从关系讲确切(props 是主流程、bus 是跳转事件)更经得起追问。

13. 「无 Proxy 环境自动降级 iframe」怎么判断?

涉及文件emi-main-app/src/components/ReWujieSubApp/src/app.tsxdefaultDegrade);src/utils/sub/index.tsuseDegrade/refreshAllApp)。

STAR 版

「判断很简单:const defaultDegrade = localStorage.getItem('degrade') === 'true' || !window.Proxy || !window.CustomElementRegistry。没有 Proxy 或有开关强制,就取 degrade,组件会把 degrade 传给下一级 <WujieVue> 并切换 polyfill 插件(无界 degragde 后降级成真 iframe,ElementPlus 那些 InstanceofPlugin 就不能开了)。用户也能在设置里手动切,切换时 watch(isDegrade) 触发 refreshAllApp() 清掉沙箱缓存。」

14. iframe 用 v-show 替代 v-if 提升 70%?数字怎么测的?

涉及文件emi-main-app/src/layout/components/appMain.vueactivatedIframeListv-show="currentRoute.path === item.path"meta.frameAlive)。

STAR 版

「iframe 是保活的:我把每个子应用的 iframe 放进 activatedIframeList,切换标签时用 v-show 控制显示/隐藏而不是 v-if 重建,配合 meta.frameAlive 决定离开时是否销毁缓存,再加 clearIframeCache 支持精确/批量清缓存。这样切回来不用重新加载子应用。」

⚠️ 要想清楚「提升 70%」这个数字代码里没有任何测量依据(没有性能打点、没有 benchmark)。面试官(问题清单特意标了)很可能问「你怎么测的、测的是首次加载还是切换」。建议改成弱化说法:「这个 70% 是我们在现场切换标签时体感 + 简单计时对比出来的,避免重建 iframe 主要省的是启动和首帧渲染成本」——要么给个你自己能复现的测法(DevTools/Performance 记切换耗时),要么别把 70% 说得那么硬。


四、Electron / 工业上位机

15. ⭐ Prisma + Electron 打包攻坚?根本原因?output 路径怎么配?

涉及文件packages/electron-app/prisma/schema.prismaelectron-builder.ymlsrc/generated/client(generated 产物在源码里)。

STAR 版

「根本原因:Prisma 默认把 generated client 和 query_engine 相关的东西塞进 node_modules,Electron 打包后路径错乱、引擎文件还经常被误删,导致运行时 PrismaClient 找不到引擎。我的做法:schema.prismaoutput = "../src/generated/client" 把 client 从 node_modules 迁到源码目录随包一起走generatorbinaryTargets = ["native","windows"] 锁平台;因为 SQLite 引擎要解压,在 electron-builder.ymlasarUnpackprisma/**sql.js 排除出 asar,这样离线数据库能正常读写。」

证据

prisma
generator client {
  provider = "prisma-client-js"
  output   = "../src/generated/client"
  binaryTargets = ["native", "windows"]
  previewFeatures = ["driverAdapters"]
}

electron-builder.ymlasarUnpack: - 'prisma/**/*' - 'node_modules/sql.js/**/*'

16. 主/渲染进程 IPC 用哪种方式?安全性怎么保证?

涉及文件src/main/api/ipc.ts(ipcMain.handle('api:request'));src/preload/index.ts(contextBridge + window.api);src/main/index.ts 的 webPreferences。

STAR 版(先说明实际情况,再补理想做法)

「大方向是 Preload 暴露白名单 API(window.api.apiRequest 等),渲染进程 ipcRenderer.invoke('api:request') 走主进程,主进程再 axios 转发到内嵌的 Express(http://localhost:端口/api),所以渲染层并不直接碰 Node 能力和业务 DB。」

⚠️ 要想清楚(重要):代码里的 webPreferencesnodeIntegration: true, contextIsolation: false,而 preload 里 if (process.contextIsolated) 为 false 时会走 else 分支把 window.api 直接挂全局。也就是说实际没吃到 contextBridge 的隔离红利,这是简历上「安全性」的隐患。面试答安全时:① 别吹 contextBridge 隔离是你项目的亮点(它没真正生效);② 准备一段真实回答:「当时为了兼容内嵌 WebRTC 远程控制那套,我们暂时开了 nodeIntegration,代价是隔离弱了,这是我后续想收口的点」。主动承认比硬撑加分。

17. 单例锁防多开?开机自启怎么配?

涉及文件src/main/index.tsapp.requestSingleInstanceLock()app.setLoginItemSettings({ openAtLogin: true }))。

STAR 版

「单例:const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) app.quit(),拿不到锁说明已有实例在跑,直接退出;second-instance 事件里可以再把已运行窗口拉起来。自启:非开发模式下 app.getLoginItemSettings() 未开启就 app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false }),Windows 上就是注册到登录启动项。」

18. electron-updater 自动更新完整流程?中途断网怎么处理?

涉及文件src/main/services/auto-update.service.ts

STAR 版(基于实际能讲的部分)

「流程:启动 15s 后 + 每 24h 检查一次;autoUpdater.autoDownload = false,发现 update-available 先通知主窗口再 downloadUpdate()download-progress 推进度;update-downloaded 后 5s quitAndInstall()。断网时 electron-updater 下载中断会抛 error,我们会记录日志,等下一轮检查重来——因为 autoDownload=false,不会强拉着用户等。」

⚠️ 要想清楚(重要):仔细看 auto-update.service.ts,真正的 checkForUpdates() 那段是被注释掉的(MVP 演示),线上走的是自建云端 /api/update/check 接口 + 主进程定时查,electron-updater 的事件/下载流程虽然挂上了,但触发链路没有真正跑通 checkForUpdates。所以:

  • 不要说「electron-updater 全链路已上线」;
  • 诚实表述:「我先搭了自建版本校验接口 + electron-updater 的下载/安装事件,但真正的自动下载发布还有一部分是 MVP,需要配合发布源和签名才能全量启用」。这块是重点追问区,务必自己把细节补准。

19. Service Manager 怎么管多服务生命周期?优雅关闭做了什么?

涉及文件src/main/api/services/manager.service.tssrc/main/index.tswill-quit

STAR 版

ServiceManager 是单例(EventEmitter),构造时统一 new 出来 MQTT Broker、MQTT client、UDP、Database、Measurement、Hardware 等十几个服务;initialize() 按顺序启动并把 started 事件透出,任一个失败只记日志不阻断其它服务启动。优雅关闭在 will-quitstopWebRTCWorker → heartbeat.stop → dataSync.stop → autoUpdate.stop → apiServer.stop → serviceManager.shutdown()shutdown() 里按逆序 mqtt.disconnect → udp.stop → mqttBroker.stop → database.disconnect,各 stop 还带了超时强制(ProMise.race 2s)防止关不掉。」


五、MQTT / UDP / 工业通信

20. ⭐ 为什么既用 MQTT 又用 UDP?

涉及文件src/main/api/services/iot-gateway.service.ts(MQTT 物联网关,JSON devList / request-response)、realtime-data-collector.service.ts + udp.service.ts(UDP 探头)。

STAR 版

「两个协议承担的职责完全不一样:MQTT 跟物联网关通信,负责开关测量、模式切换、读温度/直径/湿度这些带确认的状态量,用的是 JSON + 请求/响应(cmdId/req/flag),需要可靠和『能解释』;UDP 直接跟 FL-WAPP 探头烧高频采样,100ms 问一次 Q/M 值,只回一行数字,讲究低延迟低开销。统一用一种做不到——用 MQTT 做 100ms 高频数值轮询太重、且探头协议就是 UDP 小包;用 UDP 做状态指令又丢可靠性。所以各走各的,UDP 高频、MQTT 低频可控。」

21. 为什么自己搭 Aedes Broker 而不直接用 EMQX?

涉及文件src/main/api/services/mqtt-broker.service.ts(port 1883、concurrency 500、queueLimit 1000);manager.service.tsENABLE_INTERNAL_MQTT_BROKER

STAR 版

「这套是产线内网离线场景,不能依赖外网云端 Broker;而且只服务本机 + 网关的设备(量级小),自己起个嵌入式 Aedes(端口 1883,内存持久化,把 concurrency 调到 500、queueLimit 到 1000 防高频丢消息)就够了,省掉部署运维。ENABLE_INTERNAL_MQTT_BROKER 可开关,起不来就自动回退连外部 Broker。EMQX 适合集中式、多租户、要集群的场,这里用不上。」

22. UDP 不可靠,怎么解决丢包/乱序?「5 点去极值取中值」展开?

涉及文件iot-gateway.service.tsgetQValueUsingRealtimeCollector 收满 5 个 → processQValuesForFinalMeasurement);realtime-data-collector.service.ts(校验区间 0.5–3.5、环形缓存 slice(-100))。

STAR 版

「UDP 不可靠,我不指望一个包就定结果:多发多采 + 统计折叠。测量时连续采 5 个 Q 值(10s 超时兜底),然后在 processQValuesForFinalMeasurement 里:[...q].sort((a,b)=>a-b) → 若 3 个取中间 sorted[1],若 ≥4 个 sorted.slice(1,-1) 掐掉最大最小再取中间——这就是『5 点去极值取中值』,把随机抖动和离群点滤掉。流式数据侧维护滑动窗口(qValueData.slice(-100)),并对 Q(M) 做合理值域校验(0.5–3.5 / 1–100),超界的丢。丢包体现在『采不够 5 个』→ 走超时 + 下次重采,而不是拿到一个坏值就出结果。」

说明:请求是每 requestInterval=100ms 主动发一次,所以不是等一个回包,天然覆盖乱序——乱序通过『值域校验 + 折叠取中』吸收。

23. 「监控响应延迟小于100ms」是怎么测的?端到端还是单跳?

涉及文件realtime-data-collector.service.tsrequestInterval: 100);整个主进程/渲染层 没有延迟打点

⚠️ 要想清楚(重点):仓库里没有任何 latency 测量的代码或日志。简历上那个「99% 场景 <100ms」实际对应的是采集轮询周期 100msrequestInterval || 100),根本不是「测量的端到端延迟」。 建议改成弱化 + 真实的说法:「我这里说的 <100ms 指的是探头数据采集周期是 100ms 一帧,不是严格测出来的端到端延迟;真要讲延迟,主进程收到 UDP 到推给前端是进程内 EventEmitter + WebSocket,毫秒级,但我没做正式的 pub/sub 延迟埋点。」——把「周期」和「延迟」说清楚,别让面试官误以为你有 benchmark。

24. Web Serial / 串口通信有什么坑?权限、断线重连怎么处理?

涉及文件emi-andon-system/src/stores/webSocket.tsrequestPortopen({baudRate})getWriter 写 hex、'serial' in navigator 检测、WS 心跳 45s + 指数退避重连)。

STAR 版

「坑主要是三块:① 浏览器兼容/权限——只有 Chrome 系支持,我写 if ('serial' in navigator) 先兜底,navigator.serial.requestPort() 是用户手势授权的;② 打开失败的杂错误——Access to the port is denied(权限被拒)、The port is already openopen() already in progress 这些我全部分支 catch 给用户文案;③ 断线——WebSocket 侧做了 45s 心跳 + 1→30s 指数退避重连,串口收到服务端 towerLight 指令就用 writer.write(new Uint8Array(hex)) 下发,塔灯颜色/蜂鸣频率这样控制,延迟体感低于 500ms。」

⚠️ 想清楚:代码里 WebSocket 的自动重连其实是注释掉的reconnect() 存在但 onclose 里被注释,走到「弹窗确认 reload」)。说断线时别吹「全自动重连」,就讲「做了心跳,连接意外断开会给用户弹窗确认重载」,把注释这段的真实行为讲准。另外「500ms 设备可视化延迟」无明确打点,属体感值,弱化。


六、可视化 / Three.js

25. Three.js 场景加载优化具体做了什么?GLB/GLTF 轻量化(压缩/Draco/LOD)?

涉及文件emi-digital-twins/src/hooks/useThree/index.ts(GLTFLoader、RGBELoader、EffectComposer/OutlinePass/FXAA、CSS2DRenderer、clean 释放);hooks.git logrefactor: 重构hooks,移除时节省内存)。

STAR 版

「我把场景/相机/光照/模型加载/后处理全封成 useThree 自定义 hook(可复用),模型用 GLTFLoader 异步加载、环境用 RGBELoader 打光;后处理走 EffectComposer(RenderPass + OutlinePass 轮廓光 + FXAA 抗锯齿 + OutputPass);用 CSS2DRenderer 给部件挂信息标签、Raycaster 拾取联动轮廓高亮;卸载时 clean() 里对材质/几何体逐个 dispose()cancelAnimationFrameforceContextLoss,避免切页面后 GPU/显存泄漏。」

⚠️ 要想清楚(重点):简历吹的「轻量化:压缩 / Draco / LOD 都没实现」——代码里只有 GLTFLoader 直接 load,没有 DracoLoader、没有 glTF 压缩管线、没有 LOD(Level of Detail)。想清楚这题怎么答:

  • 要么如实说「模型这版是美术给的单描边 glb,直接 GLTFLoader 加载,Draco/LOD 是后续方向」;
  • 要么真补一个 DracoDecoder 再讲。别让面试官顺着简历问压缩比、LOD 切换策略,答不上来就凉。

26. 「交互响应延迟≤50ms」怎么测的?Raycaster 在复杂模型会不会卡?

涉及文件emi-digital-twins/src/hooks/useThree/index.tsonPointerMove → raycaster.intersectObject(scene, true) + boundsTree 无);stats.js 仅监控 FPS。

STAR 版

「交互是 pointermove 里每帧 raycaster.intersectObject(scene, true) 递归拾取 + OutlinePass 高亮。单模型阶段性能没问题;Raycaster 在多高面模型 + 每帧全量 intersect 时会退化,我这边只是用 intersectObject(...,true) 递归 + 只在有效命中才更新标签,还没上 BVH 之类的加速。」

⚠️ 要想清楚(重点)「≤50ms」「复用率 70%」代码里没有任何测量依据stats 只是 FPS 可视化,不是交互延迟埋点。 建议讲真实逻辑:交互本身在主线程指针事件 + Raycaster,复杂度主要取决于模型顶点数;别背「≤50ms 实测」。若被追问,说「我们场景是单中等模型,体感跟手;复杂机台多模型我会考虑 WebGLRenderer per-pixel picking 或 Three 的 BVH,避免每帧全量 intersect」。


七、工程化 / 性能优化

27. ECharts 大数据量实时刷新怎么防抖/降频?

涉及文件electron-detector/src/renderer/src/views/home/components/gague.vuesetInterval 驱动 chartInstance.setOption);git log f4c1b65 更新最小刷新间隔为16.67ms945fbaa 数据点窗口管理119dac8 滑动窗口1200点

STAR 版

「实时图是 setInterval 定时 setOption 增量刷,不做每次到位就重渲全图;数据侧用滑动窗口(图上保留最近 1200 点左右,git 里有 修正滑动窗口点数为1200),缓慢推进而不是无限追加;刷新间隔做过收敛(流程 commit 提到最小 16.67ms ≈ 60fps,实际按需降低频率防高频卡顿),配合 animationDurationUpdate 关闭每帧动画避免每帧都做过渡。」

28. ⭐ 首屏从 5.87s → 1.16s?具体措施?各贡献多少?

涉及文件nuxt-website/nuxt.config.jsnuxtPrecompress gzip level9/brotli level11 + middleware encodingsPriority:["br","gzip"]、cheerio 清 SSR 冗余属性、transform-remove-console);package.jsonimagemin-optipngcompression-webpack-plugin)。

STAR 版

「官网是 Nuxt2 静态生成(npm run generate)。我做几条:生产环境 transform-remove-console 去掉调试;nuxt-precompress 预生成 .br/.gz,nginx gzip_static 直接命中静态预压缩(brotli 优先);imagemin-optipng 压图;webpack 侧配过 splitChunks 抽 vendor/styles/commons。这些是把大包从『运行时 gzip』改成『构建期预压缩 + 静态分发』的路子。」

⚠️ 要想清楚(重点)5.87s→1.16s(-80%) 这两个数在仓库里找不到任何测量报告(没有 Lighthouse 结果、没有性能埋点),而且 nuxt.config 里 splitChunksCompressionPluginextractCSS 几段其实是被注释掉的(当前生效的只有 nuxt-precompress + cheerio + transform-remove-console + imagemin)。 答法建议:别假装有蓝图数据。可以说「首屏这块我主要是预压缩 + 压图 + 去 console + 静态化,5.87→1.16 是当时用浏览器 DevTools/加载瀑布统计出来的粗值,具体每项拆分图没有保留」。诚实 + 讲清措施即可。

29. Webpack 和 Vite 构建原理区别?Bundle 体积优化/分包/Tree Shaking?

涉及文件emi-main-app/vite.config.ts(Vite + 代理 + worker 配置);nuxt-website/nuxt.config.js(webpack4 splitChunks,注释态);fund-helper-vscode/esbuild.js(esbuild 打包)。

STAR 版

「Webpack 是打包器思维:从 entry 爬依赖图、全量 bundle、后期 tree-shaking;Vite 开发期靠 ESM 原生 + esbuild 预构建(秒级冷启动),生产走 Rollup,副作用分析和摇树更激进。这俩项目里 Webpack 我做过按 vendor/styles/commons 的 splitChunks 分包思路,Vite 那边主要靠按需引入组件库 + 预构建。跨域那套我在 vite config 配了 /api-proxy 代理转发。」

⚠️:仓库里没有可复现的 bundle 体积前后对比数据。若被问「具体减了多少体积」,别给编号——SUV集成用的摇树效果没实测,写「做了组件按需、分包」即可。

30. 团队效率提升 40%、代码复用率 70% 怎么统计出来的?

涉及文件:无。

⚠️ 要想清楚(重点)这两个数没有任何统计来源。简历「团队开发效率提升 40%」「代码复用率 70%」是强表述,面试官极可能追问口径。 建议改为能自圆其说的描述:「复用率那块是我们把登录鉴权、主题、路由守卫、iframe keep-alive 抽成公共组件/hook(在 emi-main-app/src/components/ReWujieSubAppuseThree 这些量化的可复用模块上体现),60–70% 是我按『新模块里直接复用已有封装/公共组件/公共指令』占比粗估的,不是正规埋点」——把「口径」主动摊开,反而显得有工程思维。


八、个人开源项目

31. ⭐ 反爬和伪装机制,合规风险怎么看?

涉及文件zhihu-fisher-vscode/src/core/zhihu/puppeteer/index.tsObject.defineProperty(navigator,'webdriver',...) 置 undefined + UA 伪造 + cookie 注入);src/core/utils/code-generator.ts(动态生成伪装代码片段);disguise-manager.ts / sidebar-disguise-manager.ts(失焦伪装成代码界面/项目文件)。

STAR 版(把工具性质讲成「个人阅读 / 无障碍」)

「这个插件定位是个人阅读辅助工具,通过 Puppeteer 打开我自己的知乎账号(带 cookie),用 page.evaluateOnNewDocumentnavigator.webdriver 置空、伪装 UA、失焦时生成一段随机代码来做界面伪装(避免工作时被同事看到在刷知乎),本地解析后在自己 VSCode 里沉浸阅读。老实说它有被平台风控的风险,我在 README 标注了『仅供学习/自用,请遵守知乎用户协议』,封号风险我自己评估过、也尽量控制采集频率不那么激进。」

⚠️ 要想清楚:合规是被重点追问的点。① 别把「伪装绕过反爬」说得太得意,强调自用 + 低频率 + 遵守 ToS 的边界感;② 准备好「有没有考虑过被封号/被官方下架」的直接回答:说明你有账号被风控预案(cookie 失效检测、扫码重登),及插件本体不该助长平台滥用。

32. networkidle1 + 5 秒超时兜底怎么调出来的?会不会抓不全?

涉及文件zhihu-fisher-vscode/src/core/zhihu/sidebar/recommend.tswaitForNetworkIdle({timeout:5000}) 在 try/catch 里,超时后继续解析;simulateHumanScroll + 骨架屏等待);src/core/zhihu/puppeteer/index.ts

STAR 版

「知乎是长连接 + 不断请求,waitForNetworkIdle 可能永远等不到——所以我 gotodomcontentloaded + 30s 超时进页面,然后 try { await page.waitForNetworkIdle({ timeout: 5000 }) } catch { 继续 },5s 或网络空闲先到先走。为了不抓不全,我再叠加了 simulateHumanScroll 模拟真人滚动,并轮询等骨架屏(section.skeleton)消失,页面真的把内容挤出来了才解析。所以 5s 不是拍脑袋:太短内容没滚出来,太长卡住,我先滚屏再用空闲时限兜底。」

⚠️ 想清楚:简历写「networkidle2」,实际代码是 page.goto(…waitUntil:'domcontentloaded') + waitForNetworkIdle({timeout:5000})(有的页面是 networkidle0,如 search/follow)。面试被追问「是 networkidle0 还是 2、5s 会不会漏」,答「我们用 domcontentloaded 进页面 + 固定 5s 空闲等待 + 滚动/骨架屏补全,不靠单一 networkidle 旗标」,就不容易被抓漏洞。另注意 goto 注释写"60秒超时"但值是 30000(30s),描述时用 30s 别照抄注释。

33. VSCode 插件 8000+ 安装量,怎么做反馈收集和迭代?差评怎么处理?

涉及文件zhihu-fisher-vscode/fund-helper-vscode/(README、CHANGELOG、buyMeCoffee 打赏入口、fund-helper-0.1.0…0.4.x.vsix 版本迭代、git log)。

STAR 版

「开源迭代主要靠 GitHub issue / Star / 打赏反馈,版本号一路迭代(fund 从 0.1.0 迭代到 0.4.9),有典型 issue(比如浏览器下载不完整、win11 卡顿、blocked insecure private network 的跨域问题)我在 puppeteer/index.ts 里都做了针对性处理并写进启动参数。差评我就事论事:先复现,能修的发补丁,修不了的说明边界。」

⚠️ 想清楚:8000+ 安装量(zhihu)在仓库 stat 里无法直接验证;备考一个「你印象最深的一条 issue 是什么、怎么修的」的真实故事(上面 win11 性能—--disable-features=UseEcoQoSForBackgroundProcess 那条就是现成素材)。

34. 基金助手里「四层代理降级」每层解决什么?

涉及文件fund-helper-vscode/web/cf.js(Cloudflare Worker,TARGET_MAP 直接拼接路径做 API 转发 + Origin 白名单);web/vite.config.ts/api-proxy/* 开发代理);src/fundService.tsfetchJsonProxy(VSCode host 代理);web/netlify/functions/proxy.ts(Netlify Serverless,已注释)。

STAR 版

「Web 端直连天天基金/东方财富接口会被浏览器 CORS 挡死,我做了降级代理链:① 开发环境走 Vite dev proxy/api-proxy/fundmob 等 rewrite 转发);② 部署到 Cloudflare Pages 时走 Cloudflare Workercf.js,按路径映射到 fundmobapi.eastmoney.com 等,并做 Origin 白名单);③ VSCode 插件端走宿主进程代理fetchJsonProxy 在 extension host 里 fetch,天然无 CORS)并带 12s AbortController 超时;④ Netlify 侧也预置过 Serverless function 做兜底转发。这样多环境(浏览器/插件/本地/云端)都能取到数据,前端按环境选可用代理层降级。」

⚠️ 想清楚(重要)web/netlify/functions/proxy.ts 现在是整个 return {} 且逻辑全被注释的——也就是 Netlify 那层当前是禁用/未完成的,「四层都可用」是被美化的。答的时候要么承认「Netlify 那层当时留了雏形还没启用,实际走 Worker / Vite 代理 / host 代理三层」,要么先把那个 function 补完再吹四层。面试官大概率会点着这个文件问。


九、软技能 / 行为面试(35–38,无代码依据)

35. 和同事/产品因为技术方案分歧,怎么解决?

STAR 版(用真实素材)

「做回潮率检测仪时,产品想尽快上线先用请求-响应版本,我想要改成持续 UDP 采集才能解决现场时序/无响应。我没硬扛,先在现场用日志把『偶发无响应导致 UI 卡死』的 case 摆出来,再给出重构方案 / 风险 / 工作量,让数据说话,最后产品和硬件同事都同意先上轮询版。我的原则是分歧靠证据和方案对比收敛,不靠职位。」

36. 在职为什么换工作?期望得到什么?

(个人问题,按真实动机答)建议逻辑:「在中恒把微前端/Electron/工业上位机一条链做成型了,想换个能继续碰全栈 + 微前端/Electron 真实业务场景的团队,把架构和 Node 后端这一层做深。」结合反问环节问团队技术栈是否匹配、晋升路径。

37. 往全栈深耕还是架构/管理?

「短期往全栈 + 架构:在上位机里我已经承担了主进程/后端/前端三重角色,想继续把 Node 服务端、数据库建模和工程化做深;管理权限是后话,我更愿意先靠技术方案影响力带人。」

38. 一周内到岗,交接有无影响?

「在职能按 Leave 流程做交接,当前主要负责的项目文档、代码评审都留有记录,一周交接压力可控,可提前对齐。」(你觉得风险大就补充:可远程配合交接一到两周。)


十、总结:⚠️ 需要你自己补强的无依据说法清单(一定要逐个看)

  1. 7×24 故障率 / 稳定性「真实数据」(题 4):无 uptime/故障率统计,只有兜底机制。
  2. iframe v-show 提升 70%(题 14):无测量,只能讲机制 + 体感。
  3. contextBridge 安全性 / IPC 隔离(题 16):实际 contextIsolation:falsenodeIntegration:true,bridge 没真正生效——主动认坑。
  4. electron-updater 全链路自动更新(题 18):checkForUpdates() 被注释,真正跑的是自建版本接口,别吹全链路。
  5. 监控响应延迟 <100ms(题 23):实际指 100ms 采集周期,无延迟埋点。
  6. Web Serial 自动断线重连(题 24):代码里自动重连被注释,实际是弹窗确认 reload。
  7. Three.js 轻量化(Draco/LOD/压缩)(题 25):代码没有,只有 GLTFLoader。
  8. 交互响应 ≤50ms、复用率 70%(题 26):无测量依据。
  9. 官网 5.87s→1.16s(-80%)(题 28):仓库无测量报告,且 splitChunks/CompressionPlugin 是注释态。
  10. 团队效率 40%、复用率 70%(题 30):无口径/无统计,主动讲「粗估」。
  11. zhihu networkidle2(题 32):实际是 domcontentloaded + waitForNetworkIdle(5s)(部分页面 networkidle0)。
  12. 反爬伪装合规(题 31):边界话术要准备好,强调自用 + 低频率 + 遵守 ToS。
  13. 四层代理降级(题 34):Netlify 那层当前注释禁用,别吹四层全可用。

说明:以上 ⚠️ 是基于仓库实读代码得出的结论,避免你在面试中被「细节陷阱题」点穿;凡标注项都请按我给的建议口径,自己再打磨成更贴近事实、更显真实的话术。

前端开发工程师 · 面试准备