老浏览器缺 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

完整机制(每个现象都对上):

  1. 点保存 → 某段调 .at(-1) → 抛 TypeError
  2. 错误被 Vue errorHandler / 某层 try-catch 吞掉 → Console 啥都不显示
  3. dialogVisible 没被置 true → 弹窗不渲染 → “点了没反应”
  4. 列表页那条路径不碰 .at() → 正常(误导成”只有保存坏”)
  5. 开发环境是现代 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。