全局变量易引发状态不一致、bundle体积增大等问题,应优先用模块作用域封装状态,跨组件通信选用事件或状态库,仅只读且初始化即确定的配置类变量可接受。

全局变量为什么在 JavaScript 里容易出问题
全局变量不是语法错误,但它是运行时隐患的温床。最直接的表现是:两个模块或函数意外修改了同一个 window.count 或 globalThis.userId,导致状态不一致、难以复现的 bug。更隐蔽的是,打包工具(如 Webpack)无法对全局变量做 tree-shaking,它会阻止死代码消除,让 bundle 体积变大。
常见错误现象包括:
- 页面刷新后某个计数器从 5 变成 0,但没调用重置逻辑
- 组件 A 修改了
currentUser,组件 B 却读到旧值 - 测试环境里多个 test case 相互污染,因为共享了同一份全局状态
替代方案:用模块作用域封装状态
ES Module 天然支持私有状态管理——把变量声明在模块顶层,只通过导出的函数访问,就能避免外部直接篡改。
const _token = localStorage.getItem('auth_token');
let _user = null;
export function getUser() {
return _user;
}
export function setUser(user) {
_user = user;
}
export function clearUser() {
_user = null;
}
这种方式比 var globalUser = null 安全得多:它不可被其他模块直接赋值,且生命周期与模块加载绑定。注意不要导出原始变量(如 export let user = null),否则仍可被外部修改。
立即学习“Java免费学习笔记(深入)”;
需要跨组件通信?优先选事件或状态库,而不是挂 window
当多个 React/Vue 组件需要响应同一状态变化时,强行用 window.appState + dispatchEvent 是在重复造轮子,且缺乏类型提示和调试支持。
推荐路径:
- 小项目:用
CustomEvent+window.dispatchEvent(仅限简单通知,不传复杂对象) - 中大型项目:用
useReducer+ Context(React)、Pinia(Vue)或轻量级zustand - 纯 JS 环境:封装一个简单的发布-订阅类,比如
createStore({ initialState, reducers })
别碰 window.myAppStore —— 它绕过了所有现代状态管理的设计约束,比如不可变更新、批量更新、时间旅行调试。
什么情况下可以接受全局变量
极少数场景下,全局变量是合理且必要的,但必须满足两个条件:**只读** + **初始化即确定**。
例如:
-
环境配置:
const ENV = Object.freeze({ API_URL: 'https://api.example.com' }) - 常量枚举:
const STATUS = Object.freeze({ PENDING: 'pending', SUCCESS: 'success' }) - 第三方 SDK 实例(如
Analytics.init()),且该实例本身设计为单例
关键区别在于:这些值不会在运行时被“更新”,也不承载业务逻辑的状态流转。一旦你发现要写 window.cartItems.push(item),就该立刻停下来,换状态管理方案。
真正难的不是“能不能用全局变量”,而是判断某个变量是否真的属于应用层状态——还是只是配置、缓存或副作用载体。这个边界模糊时,宁可多封装一层函数,也不要图省事扔进全局。











