老浏览器缺 Array.prototype.at() → 现代产物静默炸(点了没反应、Console 全空)
某客户机 BPM 流程表单 /bpm/manager/form/edit 点保存没弹窗、Console 零报错。绕了一大圈查部署 / nginx / 缓存全干净,根因是客户机那台 Chrome 86(2020-11,从没更新)缺 Array.prototype.at()(Chrome 92+ 才有),前端产物里 .at(-1) 抛 TypeError 被框架吞掉 → 弹窗不渲染。换新浏览器即解。
通法,不限于某个库。
症状(每条都误导人往”部署 / 缓存”想)
- 点「保存」按钮毫无反应:点击探针确认点击确实落在
<button>保存</button>、disabled=false,但点击后 DOM 无任何新增(弹窗根本没渲染)。 - 无 console error、无 unhandledrejection(错误被吞)。
- 列表页
GET /xxx/page正常返回——给人”后端 / 请求层都好,就保存坏”的错觉。 - 同一份 build 在开发环境(现代浏览器)实测完全正常——最迷惑的一点,让人死盯”客户机部署 / nginx 哪里不一样”。
源码侧:点保存 = handleSave() 只做 dialogVisible.value = true 开 <Dialog>,纯客户端不发请求。所以”没弹窗” = handleSave 这条路径中途抛错没走到赋值。
为什么把所有”显而易见”的方向都排除了
部署链路三处指纹三连一致,全是死路:
| 查了什么 | 结果 |
|---|---|
盘上 web/index.html 引用的入口 JS 哈希 | 全对 = 盘上是新 build |
| 产物 banner 上的库版本号 | 三处版本全对 |
nginx root + curl GET / 出口引用 | 吐的就是新 build |
浏览器 performance.getEntriesByType('resource') 实际加载的 chunk 文件名 | 浏览器吃到的就是新的 |
navigator.serviceWorker.getRegistrations() | 0 个,无 SW 缓存层 |
| Disable cache + Ctrl+Shift+R 硬刷 | 症状不变 = 排除浏览器缓存 |
→ 盘上 / nginx 出口 / 浏览器加载三处一致,部署完全没问题。剩下唯一的变量就是浏览器本身。
根因
navigator.userAgent = ...Chrome/86.0.4240.198...。这台 Win Server 装的是 2020-11 的 Chrome 86。
前端 chunk 里 grep 到 10+ 处 .at(-1)(a.at(-1) / items.at(-1) / inlineQueue.at(-1) …)。Array.prototype.at() 是 Chrome 92 / 2021-07 才落地的运行时方法,Chrome 86 上 [].at 根本不存在,x.at(-1) 直接抛 TypeError: x.at is not a function。
完整机制(每个现象都对上):
- 点保存 → 某段调
.at(-1)→ 抛TypeError - 错误被 Vue errorHandler / 某层 try-catch 吞掉 → Console 啥都不显示
dialogVisible没被置 true → 弹窗不渲染 → “点了没反应”- 列表页那条路径不碰
.at()→ 正常(误导成”只有保存坏”) - 开发环境是现代 Chromium,有
.at()→ 一切正常(解释”同 build 那边好”的矛盾)
0 成本自证(以后遇到先敲这个)
在出问题的浏览器 Console:
typeof [].at // "undefined" = 老浏览器缺 API,坐实;现代浏览器返回 "function"
navigator.userAgent // 看 Chrome 大版本,<92 就是它通法:现代前端产物在某台机器上”某功能点了没反应 + Console 全空 + 别的机器正常”,先看
navigator.userAgent浏览器版本,再typeof [].at/typeof structuredClone/[].findLast/Object.hasOwn逐个探近年新增 API。被框架吞掉的TypeError不进 Console,最像”什么都没发生”。
修复(推荐顺序 A > B > C)
A. 换浏览器(本次已采用并验证):客户机 Chrome 升到 ≥92 / 装新版 Edge。前端一个字不改,立即恢复。 代价:这是”让访问者用新浏览器”的运维约定,若仍有人用 Chrome 86 访问会复现。
B. 针对性 polyfill(已知只缺 .at 一个):前端入口 main.ts 最顶:
import 'core-js/modules/es.array.at'
import 'core-js/modules/es.string.at'不动构建架构、产物几乎不变大。
⚠️ 上线前在构建机全量扫所有 chunk 的 Chrome 87–92 区间 API(.at / findLast / Object.hasOwn / structuredClone …),一次补全别漏网。
C. 完整 legacy(@vitejs/plugin-legacy):
legacy({ targets: ['chrome >= 86'], modernPolyfills: ['es.array.at', 'es.string.at'] })⚠️ 必踩的坑:plugin-legacy 默认只给不支持 ESM 的老浏览器(nomodule 分支)注入 polyfill。Chrome 86 支持 ESM,会被当”现代浏览器”走 module 分支,那条分支默认不带 polyfill,.at 照样缺照样炸。必须显式 modernPolyfills 让现代 chunk 也注入,否则加了等于没加。代价是双份 bundle + SystemJS 体积 / 构建时间。
已知就缺一个 API,没必要直接上 C。