如何避免全局变量?以Shift按键状态追踪场景为例
处理全局可变状态的实用方案
针对你提到的Shift键状态追踪场景,以及通用的全局可变状态管理需求,这里有几种兼顾简洁性和封装性的方案:
一、Shift按键场景的轻量封装方案
1. 立即执行函数(IIFE)闭包
用IIFE封装内部状态,只暴露获取状态的方法,既避免全局可变变量,又不会像类那样繁琐:
const getIsShiftPressed = (() => { let isShift = false; // 只绑定一次事件监听 document.addEventListener("keydown", (e) => { isShift = e.shiftKey; }); document.addEventListener("keyup", (e) => { isShift = e.shiftKey; }); return () => isShift; })(); // 使用方式 if (getIsShiftPressed()) { console.log("Shift键已按下"); }
内部的isShift变量不会污染全局作用域,外部只能通过getIsShiftPressed()读取状态,无法直接修改。
2. 冻结对象+Getter
如果希望用更直观的属性访问方式,可以结合闭包和冻结对象,确保容器本身不可被修改:
const KeyState = (() => { const state = { isShift: false }; document.addEventListener("keydown", (e) => { state.isShift = e.shiftKey; }); document.addEventListener("keyup", (e) => { state.isShift = e.shiftKey; }); // 冻结返回的对象,防止外部篡改结构 return Object.freeze({ get isShift() { return state.isShift; } }); })(); // 使用方式 if (KeyState.isShift) { console.log("Shift键已按下"); }
KeyState是冻结的,外部无法添加/修改它的属性,只能通过isShift getter读取内部状态。
二、通用全局可变状态管理方案
1. 单一状态的闭包封装
对于独立的单个全局状态(比如某个开关、计数器),用上面的IIFE方案足够,简洁且安全。
2. 多状态的集中管理类
如果需要追踪多个相关全局状态(比如同时管理Shift、Ctrl、CapsLock的状态),可以用类封装私有状态和统一的事件处理逻辑,避免代码零散:
class KeyStateManager { // 私有状态,外部无法直接访问 #state = { isShift: false, isCtrl: false, isCapsLock: false }; constructor() { this.bindEventListeners(); } bindEventListeners() { document.addEventListener("keydown", this.handleKeyEvent.bind(this)); document.addEventListener("keyup", this.handleKeyEvent.bind(this)); // CapsLock是切换状态,单独处理 document.addEventListener("keydown", (e) => { if (e.key === "CapsLock") { this.#state.isCapsLock = !this.#state.isCapsLock; } }); } handleKeyEvent(e) { this.#state.isShift = e.shiftKey; this.#state.isCtrl = e.ctrlKey; } // 只暴露getter,禁止外部修改状态 get isShift() { return this.#state.isShift; } get isCtrl() { return this.#state.isCtrl; } get isCapsLock() { return this.#state.isCapsLock; } } // 全局实例化一次,避免重复绑定事件 const keyState = new KeyStateManager(); // 使用方式 if (keyState.isShift && keyState.isCtrl) { console.log("Shift+Ctrl已按下"); }
这里用ES6私有字段(#state)确保内部状态不会被外部篡改,同时统一管理所有相关事件,便于后续维护和扩展。
关于全局变量的取舍
如果是小型项目、快速原型或者临时脚本,直接用全局变量确实可以接受,开发成本低。但在中大型项目、涉及安全逻辑的场景中,封装后的方案更优:
- 避免状态被意外修改(比如其他脚本误改
isShift值) - 降低全局作用域污染,减少命名冲突
- 便于后续扩展(比如添加状态变更的监听、日志)
内容的提问来源于stack exchange,提问作者Cameron Snell
相关产品推荐
相关产品推荐

