JavaScript是否用设计模式取决于是否遇到重复的结构问题;单例应注重可控共享与生命周期,Observer比EventEmitter更适前端,工厂函数比抽象类更契合JS动态性。
JavaScript 里用不用设计模式,不取决于“是不是高级工程师”,而取决于你有没有遇到**重复的结构问题**——比如多个地方手动管理状态同步、频繁创建相似但略有差异的对象、事件响应逻辑越来越难追踪。这时候硬写逻辑只会让代码变脆弱,而一个轻量、明确的模式能立刻划清职责边界。
很多人一上来就用 class + static instance 实现单例,结果发现测试时无法重置状态、模块热更新失败、或者和 ESM 的顶层作用域冲突。
真正该用单例的场景是:需要跨模块复用且初始化开销大(如日志器、配置加载器、WebSocket 连接管理器),同时要求生命周期可预测。
new 调用:const Logger = (() => {
let instance = null;
return () => {
if (!instance) {
instance = { log: (msg) => console.log(`[LOG] ${msg}`) };
}
return instance;
};
})();fetch 配置);改用 init() 方法并返回 Promise,调用方显式等待React 或 Vue,优先考虑 Composition API / useMemo 等机制替代手写单例,避免脱离框架生命周期Observer 模式比 EventEmitter 更适合前端状态流Node.js 的 EventEmitter 在浏览器里容易引发内存泄漏(忘记 off)、事件名拼错无提示、无法链式响应。而原生 Observer(如 MutationObserver)或轻量实现(如 tiny-observer)更贴合 DOM 更新节奏。
Proxy + 自定义通知,而非深克隆对比:const createObservable = (obj) => {
return new Proxy(obj, {
set(target, key, value) {
target[key] = value;
notify(key, value); // 触发订阅
return true;
}
});
};render 函数里直接调用 subscribe();应放在 useEffect 或 onMounted 中,并确保 unsubscribe 被正确调用Subject 实例,否则响应会指数级放大class 继承在 JS 中常被误用:为了一点行为差异就建一堆子类,结果每个子类只重写一个方法,其余全是复制粘贴。JS 没有编译期类型检查,abstract class 只是徒增心智负担。
const createButton = ({ type = 'primary', size = 'md', onClick }) => ({
render() {
const cls = `btn btn-${type} btn-${size}`;
return ``;
}
});createPrimaryButton、createOutlineButton),而不是塞进一个巨型 switch
update(config) 方法,避免外部直接赋值破坏一致性Mediator 去解耦两个本就不该通信的模块,或者把 Decorator 写成嵌套 5 层的高阶函数。模式是症状的解法,不是代码的装饰品。