面试问题清单 · 逐题回答草稿(含仓库代码实证)
使用说明
- 每道题给出:问题原文 → 涉及文件/代码位置(已核对过,可回去确认)→ STAR 口语化回答(按 30–60 秒压口述版)→ ⚠️ 无依据 / 需要想清楚的说法。
- 带 ⚠️ 的条目表示:代码里没找到能支撑这个说法的实现或测量依据,面试前务必自己补一段话术,否则追问会露馅。
- 所有路径均相对各自仓库根目录。口语化回答仅供你练习,别背稿,讲你真实做过的部分。
〇、仓库 → 问题清单映射表
| 问题 | 对应仓库 | 核心文件 |
|---|---|---|
| 1–4, 15–24 | workspace/electron-detector | packages/electron-app/src/main/** |
| 5–9 | 通用 Vue 知识 + emi-main-app(Pinia store)、electron-detector(Composition API) | 见各题 |
| 10–14 | workspace/emi-main-app(Wujie 微前端) | src/components/ReWujieSubApp/**、src/layout/components/appMain.vue |
| 15–19 | workspace/electron-detector | packages/electron-app/src/main/index.ts、services/** |
| 20–24 | workspace/electron-detector + emi-andon-system(Web Serial) | services/iot-gateway.service.ts、udp.service.ts 等 |
| 25–26 | workspace/emi-digital-twins | src/hooks/useThree/** |
| 27 | electron-detector(ECharts 实时图)、nuxt-big-screen(大屏) | src/renderer/src/views/home/components/gague.vue |
| 28 | workspace/nuxt-website(中恒智造官网) | nuxt.config.js、package.json |
| 29 | emi-main-app(Vite)、nuxt-website(Webpack)、fund-helper-vscode(esbuild) | vite.config.ts、nuxt.config.js |
| 30 | — (无量化依据) | — |
| 31–32 | workspace/zhihu-fisher-vscode | src/core/zhihu/puppeteer/index.ts、sidebar/recommend.ts、utils/code-generator.ts |
| 33 | zhihu-fisher-vscode、fund-helper-vscode | README.md、CHANGELOG.md |
| 34 | workspace/fund-helper-vscode/web | cf.js、netlify/functions/proxy.ts、vite.config.ts、src/fundService.ts |
| 35–38 | 软技能(无代码) | — |
补充:workpace 里还有
huidong-cmcc-*(惠东小程序 + Express/Prisma/MySQL 后端)、mobile-smzdm-bot、zh-keyboard(触屏键盘)、Kiro-auto-register等,主要支撑「全栈/后端」定位,未在清单中单独出题,但可在 15/34 时顺带提后端功底。
一、开场 & 项目真实性核验
1. 请用 3 分钟介绍最有代表性的「智能回潮率检测仪」,重点讲个人负责的部分。
涉及文件:electron-detector/packages/electron-app/src/main/**;prisma/schema.prisma;README.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.ts和iot-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.ts;services/data-sync.service.ts;services/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/computed、effectScope。手写一个骨架我能写出来。」
⚠️:这题仓库里没有实现,纯靠源码理解。建议真的能手写一遍 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.ts、formula.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 版(当场写):
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/__defineEmits,const/ref声明提升为局部变量、编译产物更小;模板里的变量引用会被编进 setup 作用域,天然 TS 类型推导;同时也因为变量是局部闭包,模板编译期的静态提升(hoisting)和cacheHandlers也能更好地命中。」
⚠️:仓库无对应实现,纯知识。
三、微前端(Wujie)
10. ⭐ Wujie 核心隔离原理?
涉及文件:emi-main-app/src/components/wujie/wujie-core/src/(vendored 源码:shadow.ts、iframe.ts、proxy.ts、sandbox.ts、template.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 沙箱 + 动态
replaceHTML 进 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.props把dataTheme、layoutTheme、userParams(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.tsx(defaultDegrade);src/utils/sub/index.ts(useDegrade/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.vue(activatedIframeList、v-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.prisma;electron-builder.yml;src/generated/client(generated 产物在源码里)。
STAR 版:
「根本原因:Prisma 默认把 generated client 和
query_engine相关的东西塞进node_modules,Electron 打包后路径错乱、引擎文件还经常被误删,导致运行时PrismaClient找不到引擎。我的做法:schema.prisma里output = "../src/generated/client"把 client 从 node_modules 迁到源码目录随包一起走;generator用binaryTargets = ["native","windows"]锁平台;因为 SQLite 引擎要解压,在electron-builder.yml里asarUnpack把prisma/**和sql.js排除出 asar,这样离线数据库能正常读写。」
证据:
generator client {
provider = "prisma-client-js"
output = "../src/generated/client"
binaryTargets = ["native", "windows"]
previewFeatures = ["driverAdapters"]
}electron-builder.yml 中 asarUnpack: - '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。」
⚠️ 要想清楚(重要):代码里的 webPreferences 是 nodeIntegration: true, contextIsolation: false,而 preload 里 if (process.contextIsolated) 为 false 时会走 else 分支把 window.api 直接挂全局。也就是说实际没吃到 contextBridge 的隔离红利,这是简历上「安全性」的隐患。面试答安全时:① 别吹 contextBridge 隔离是你项目的亮点(它没真正生效);② 准备一段真实回答:「当时为了兼容内嵌 WebRTC 远程控制那套,我们暂时开了 nodeIntegration,代价是隔离弱了,这是我后续想收口的点」。主动承认比硬撑加分。
17. 单例锁防多开?开机自启怎么配?
涉及文件:src/main/index.ts(app.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后 5squitAndInstall()。断网时 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.ts;src/main/index.ts 的 will-quit。
STAR 版:
「
ServiceManager是单例(EventEmitter),构造时统一 new 出来 MQTT Broker、MQTT client、UDP、Database、Measurement、Hardware 等十几个服务;initialize()按顺序启动并把started事件透出,任一个失败只记日志不阻断其它服务启动。优雅关闭在will-quit:stopWebRTCWorker → 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.ts 的 ENABLE_INTERNAL_MQTT_BROKER。
STAR 版:
「这套是产线内网离线场景,不能依赖外网云端 Broker;而且只服务本机 + 网关的设备(量级小),自己起个嵌入式 Aedes(端口 1883,内存持久化,把
concurrency调到 500、queueLimit到 1000 防高频丢消息)就够了,省掉部署运维。ENABLE_INTERNAL_MQTT_BROKER可开关,起不来就自动回退连外部 Broker。EMQX 适合集中式、多租户、要集群的场,这里用不上。」
22. UDP 不可靠,怎么解决丢包/乱序?「5 点去极值取中值」展开?
涉及文件:iot-gateway.service.ts(getQValueUsingRealtimeCollector 收满 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.ts(requestInterval: 100);整个主进程/渲染层 没有延迟打点。
⚠️ 要想清楚(重点):仓库里没有任何 latency 测量的代码或日志。简历上那个「99% 场景 <100ms」实际对应的是采集轮询周期 100ms(requestInterval || 100),根本不是「测量的端到端延迟」。 建议改成弱化 + 真实的说法:「我这里说的 <100ms 指的是探头数据采集周期是 100ms 一帧,不是严格测出来的端到端延迟;真要讲延迟,主进程收到 UDP 到推给前端是进程内 EventEmitter + WebSocket,毫秒级,但我没做正式的 pub/sub 延迟埋点。」——把「周期」和「延迟」说清楚,别让面试官误以为你有 benchmark。
24. Web Serial / 串口通信有什么坑?权限、断线重连怎么处理?
涉及文件:emi-andon-system/src/stores/webSocket.ts(requestPort、open({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 open、open() 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 log(refactor: 重构hooks,移除时节省内存)。
STAR 版:
「我把场景/相机/光照/模型加载/后处理全封成
useThree自定义 hook(可复用),模型用 GLTFLoader 异步加载、环境用 RGBELoader 打光;后处理走 EffectComposer(RenderPass + OutlinePass 轮廓光 + FXAA 抗锯齿 + OutputPass);用 CSS2DRenderer 给部件挂信息标签、Raycaster 拾取联动轮廓高亮;卸载时clean()里对材质/几何体逐个dispose()、cancelAnimationFrame、forceContextLoss,避免切页面后 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.ts(onPointerMove → 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.vue(setInterval 驱动 chartInstance.setOption);git log f4c1b65 更新最小刷新间隔为16.67ms、945fbaa 数据点窗口管理、119dac8 滑动窗口1200点。
STAR 版:
「实时图是
setInterval定时setOption增量刷,不做每次到位就重渲全图;数据侧用滑动窗口(图上保留最近 1200 点左右,git 里有修正滑动窗口点数为1200),缓慢推进而不是无限追加;刷新间隔做过收敛(流程 commit 提到最小 16.67ms ≈ 60fps,实际按需降低频率防高频卡顿),配合animationDurationUpdate关闭每帧动画避免每帧都做过渡。」
28. ⭐ 首屏从 5.87s → 1.16s?具体措施?各贡献多少?
涉及文件:nuxt-website/nuxt.config.js(nuxtPrecompress gzip level9/brotli level11 + middleware encodingsPriority:["br","gzip"]、cheerio 清 SSR 冗余属性、transform-remove-console);package.json(imagemin-optipng、compression-webpack-plugin)。
STAR 版:
「官网是 Nuxt2 静态生成(
npm run generate)。我做几条:生产环境transform-remove-console去掉调试;nuxt-precompress预生成 .br/.gz,nginxgzip_static直接命中静态预压缩(brotli 优先);imagemin-optipng压图;webpack 侧配过splitChunks抽 vendor/styles/commons。这些是把大包从『运行时 gzip』改成『构建期预压缩 + 静态分发』的路子。」
⚠️ 要想清楚(重点):5.87s→1.16s(-80%) 这两个数在仓库里找不到任何测量报告(没有 Lighthouse 结果、没有性能埋点),而且 nuxt.config 里 splitChunks、CompressionPlugin、extractCSS 几段其实是被注释掉的(当前生效的只有 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/ReWujieSubApp、useThree 这些量化的可复用模块上体现),60–70% 是我按『新模块里直接复用已有封装/公共组件/公共指令』占比粗估的,不是正规埋点」——把「口径」主动摊开,反而显得有工程思维。
八、个人开源项目
31. ⭐ 反爬和伪装机制,合规风险怎么看?
涉及文件:zhihu-fisher-vscode/src/core/zhihu/puppeteer/index.ts(Object.defineProperty(navigator,'webdriver',...) 置 undefined + UA 伪造 + cookie 注入);src/core/utils/code-generator.ts(动态生成伪装代码片段);disguise-manager.ts / sidebar-disguise-manager.ts(失焦伪装成代码界面/项目文件)。
STAR 版(把工具性质讲成「个人阅读 / 无障碍」):
「这个插件定位是个人阅读辅助工具,通过 Puppeteer 打开我自己的知乎账号(带 cookie),用
page.evaluateOnNewDocument把navigator.webdriver置空、伪装 UA、失焦时生成一段随机代码来做界面伪装(避免工作时被同事看到在刷知乎),本地解析后在自己 VSCode 里沉浸阅读。老实说它有被平台风控的风险,我在 README 标注了『仅供学习/自用,请遵守知乎用户协议』,封号风险我自己评估过、也尽量控制采集频率不那么激进。」
⚠️ 要想清楚:合规是被重点追问的点。① 别把「伪装绕过反爬」说得太得意,强调自用 + 低频率 + 遵守 ToS 的边界感;② 准备好「有没有考虑过被封号/被官方下架」的直接回答:说明你有账号被风控预案(cookie 失效检测、扫码重登),及插件本体不该助长平台滥用。
32. networkidle1 + 5 秒超时兜底怎么调出来的?会不会抓不全?
涉及文件:zhihu-fisher-vscode/src/core/zhihu/sidebar/recommend.ts(waitForNetworkIdle({timeout:5000}) 在 try/catch 里,超时后继续解析;simulateHumanScroll + 骨架屏等待);src/core/zhihu/puppeteer/index.ts。
STAR 版:
「知乎是长连接 + 不断请求,
waitForNetworkIdle可能永远等不到——所以我goto用domcontentloaded+ 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.ts 的 fetchJsonProxy(VSCode host 代理);web/netlify/functions/proxy.ts(Netlify Serverless,已注释)。
STAR 版:
「Web 端直连天天基金/东方财富接口会被浏览器 CORS 挡死,我做了降级代理链:① 开发环境走 Vite dev proxy(
/api-proxy/fundmob等 rewrite 转发);② 部署到 Cloudflare Pages 时走 Cloudflare Worker(cf.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 流程做交接,当前主要负责的项目文档、代码评审都留有记录,一周交接压力可控,可提前对齐。」(你觉得风险大就补充:可远程配合交接一到两周。)
十、总结:⚠️ 需要你自己补强的无依据说法清单(一定要逐个看)
- 7×24 故障率 / 稳定性「真实数据」(题 4):无 uptime/故障率统计,只有兜底机制。
- iframe v-show 提升 70%(题 14):无测量,只能讲机制 + 体感。
- contextBridge 安全性 / IPC 隔离(题 16):实际
contextIsolation:false、nodeIntegration:true,bridge 没真正生效——主动认坑。 - electron-updater 全链路自动更新(题 18):
checkForUpdates()被注释,真正跑的是自建版本接口,别吹全链路。 - 监控响应延迟 <100ms(题 23):实际指 100ms 采集周期,无延迟埋点。
- Web Serial 自动断线重连(题 24):代码里自动重连被注释,实际是弹窗确认 reload。
- Three.js 轻量化(Draco/LOD/压缩)(题 25):代码没有,只有 GLTFLoader。
- 交互响应 ≤50ms、复用率 70%(题 26):无测量依据。
- 官网 5.87s→1.16s(-80%)(题 28):仓库无测量报告,且 splitChunks/CompressionPlugin 是注释态。
- 团队效率 40%、复用率 70%(题 30):无口径/无统计,主动讲「粗估」。
- zhihu
networkidle2(题 32):实际是domcontentloaded + waitForNetworkIdle(5s)(部分页面 networkidle0)。 - 反爬伪装合规(题 31):边界话术要准备好,强调自用 + 低频率 + 遵守 ToS。
- 四层代理降级(题 34):Netlify 那层当前注释禁用,别吹四层全可用。
说明:以上 ⚠️ 是基于仓库实读代码得出的结论,避免你在面试中被「细节陷阱题」点穿;凡标注项都请按我给的建议口径,自己再打磨成更贴近事实、更显真实的话术。