javascript如何管理状态_全局变量总是坏主意吗

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

全局变量为什么在 JavaScript 里容易出问题

全局变量不是语法错误,但它是运行时隐患的温床。最直接的表现是:两个模块或函数意外修改了同一个 window.countglobalThis.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),否则仍可被外部修改。

需要跨组件通信?优先选事件或状态库,而不是挂 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),就该立刻停下来,换状态管理方案。

真正难的不是“能不能用全局变量”,而是判断某个变量是否真的属于应用层状态——还是只是配置、缓存或副作用载体。这个边界模糊时,宁可多封装一层函数,也不要图省事扔进全局。