注册

一个“全局变量”引发的线上事故

你正在给一家 SaaS 客户做「企业级仪表盘」项目。

需求很简单:把 20 多个子系统的实时指标汇总到一张大屏。

为了“图省事”,你在入口文件里顺手写了这么一段:


// main.js
window.dashboard = {}; // 🔍 全局缓存
window.dashboard.cache = new Map(); // 🔍 存接口数据
window.dashboard.refresh = () => { // 🔍 供子系统调用
fetch('/api/metrics').then(r => r.json())
.then(data => window.dashboard.cache = new Map(data));
};

上线两周后,客户反馈:

“切到别的标签页再回来,大屏偶尔会白屏,刷新又好了。”

排查发现,Chrome 在内存紧张时会 回收后台标签页的 JS 堆,但 window 对象属于宿主环境,不会被回收

结果:



  • 旧的 dashboard 引用还在,但闭包里的 Map 被 GC 清掉,变成空壳;
  • 子系统继续调用 window.dashboard.refresh,拿到的永远是空数据;
  • 页面逻辑崩溃,白屏。

解决方案:三层防护,把风险降到 0


1. 表面用法:用 WeakMap 做“软引用”


// 把全局缓存改成弱引用
const cache = new WeakMap(); // 🔍 不阻止 GC
window.dashboard = {
get data() { return cache.get(document); }, // 🔍 与当前文档生命周期绑定
set data(v) { cache.set(document, v); }
};

这样当用户切走标签页、文档被卸载时,cache 自动释放,切回来重新初始化,白屏消失。


2. 底层机制:为什么 window 不会被 GC?


层级角色生命周期是否可被 GC
JS 堆闭包、普通对象无引用即回收
Host 环境window与标签页同生同灭
渲染进程GPU 纹理、DOM标签页关闭后统一清理

结论:window 是宿主对象,生命周期长于 JS 堆,挂上去的东西如果强引用 JS 对象,会导致“僵尸引用”。


3. 设计哲学:把“全局”变成“注入”


与其在 window 上挂变量,不如用 依赖注入 把作用域限制在模块内部:


// dashboard.js
export const createDashboard = () => {
const cache = new Map(); // 🔍 私有状态
return {
refresh: () => fetch('/api/metrics')
.then(r => r.json())
.then(data => cache.clear() && data.forEach(d => cache.set(d.id, d)))
};
};

// main.js
import { createDashboard } from './dashboard.js';
const dashboard = createDashboard(); // 🔍 生命周期由模块控制

应用扩展:可复用的配置片段


如果你的项目必须暴露全局 API(比如给第三方脚本调用),可以用 命名空间 + Symbol 双保险:


// global-api.js
const NAMESPACE = Symbol.for('__corp_dashboard__');
window[NAMESPACE] = { // 🔍 避免命名冲突
version: '2.1.0',
createDashboard
};

环境适配说明:



  • 浏览器:Symbol 在 IE11 需 polyfill;
  • Node:同构渲染时 window 不存在,用 globalThis[NAMESPACE] 兜底。

举一反三:3 个变体场景



  1. 微前端基座

    window.__MICRO_APP_STORE__ 传递状态,但内部用 Proxy 做读写拦截,防止子应用直接篡改。
  2. Chrome 插件 content-script

    通过 window.postMessage 与页面通信,避免污染宿主全局。
  3. Electron 主进程/渲染进程

    contextBridge.exposeInMainWorld('api', safeApi) 把 API 注入到隔离上下文,防止 Node 权限泄露。

小结:


window 当“公告栏”可以,但别把“保险箱”也挂上去。

用弱引用、模块化和依赖注入,才能把全局变量的风险真正关进笼子里。


作者:前端微白
来源:juejin.cn/post/7530893865550675968

0 个评论

要回复文章请先登录注册