当前位置: 首页 > news >正文

设计模式实战:单例・发布订阅・策略 JS 轻量用法|JS 进阶必会篇

设计模式(单例/发布订阅/策略)】+【前端中后台开发】:从【核心概念】到【落地实操】,彻底搞懂前端常用设计模式的最佳写法,避开测试残留、内存泄漏、面条码高频坑!

📑 文章目录

  • 一、开篇:为什么要学设计模式?
  • 二、概念扫盲
  • 三、单例模式:权限控制里的“唯一管家”
    • 3.1 是什么?
    • 3.2 最简单的实现
    • 3.3 用 class 实现(更贴近日常写法)
    • 3.4 实际用法:按钮权限、路由守卫
    • 3.5 单例的坑
  • 四、发布订阅模式:消息通知解耦
    • 4.1 是什么?
    • 4.2 核心:EventBus
    • 4.3 实战:消息通知中心
    • 4.4 完整示例:登录后多处联动
    • 4.5 发布订阅的坑
  • 五、策略模式:表单校验规则可插拔
    • 5.1 是什么?
    • 5.2 没策略时:容易变成“面条码”
    • 5.3 用策略重构
    • 5.4 实战:表单校验配置化
    • 5.5 策略的坑
  • 六、三者怎么选?
  • 七、小结
  • 🔍 本系列专栏导航

同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~

(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)

你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?

你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?

就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。

一天只有24小时,时间永远不够用,常常感到力不从心。

技术行业,本就是逆水行舟,不进则退。

如果你也有同样的困扰,别慌。

从现在开始,跟着我一起心态归零,利用碎片时间,来一次彻彻底底的基础扫盲

这一次,我们一起慢慢来,扎扎实实变强。

不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,

咱们一起稳步积累,真正摆脱“面向搜索引擎写代码”的尴尬。


一、开篇:为什么要学设计模式?

你可能会想:我写业务能跑就行,为什么要管设计模式?

实际工作中常见这些情况:

  • 权限控制:路由守卫、按钮权限、接口权限各处都在写,逻辑重复、难维护。

  • 消息通知:多个模块要弹 Toast,互相耦合,改一处影响一片。

  • 表单校验:不同字段用不同规则,全写在一个大if-else里,难扩展。

这些问题都可以用少量设计模式来简化。下面用单例、发布订阅、策略三种模式,搭配权限、通知、表单校验三个场景,讲清楚:日常怎么写、为什么这么写、容易踩哪些坑

⬆ 返回目录


二、概念扫盲

先把三个模式用一句话说清楚:

模式一句话适合场景
单例全局只存在一个实例,多次调用拿到同一个对象全局唯一的东西:权限管理器、通知中心、全局配置
发布订阅发布者发事件,订阅者监听,彼此解耦一对多、多对多通知:消息通知、事件总线
策略把多种“算法/规则”封装成可替换的策略同种操作多种规则:表单校验、支付方式选择

下面按「概念 → 代码 → 实战」来展开。

⬆ 返回目录


三、单例模式:权限控制里的“唯一管家”

3.1 是什么?

单例保证:不管你怎么new、怎么调用,拿到的一定是同一个实例。

⬆ 返回目录

3.2 最简单的实现

// 懒汉式单例:用到的时候才创建functioncreatePermissionManager(){letinstance=null;returnfunction(){if(!instance){instance={role:'guest',permissions:[],check(perm){returnthis.permissions.includes(perm);},setRole(role,perms){this.role=role;this.permissions=perms;}};}returninstance;};}constgetPermissionManager=createPermissionManager();constpm1=getPermissionManager();constpm2=getPermissionManager();console.log(pm1===pm2);// true

核心是闭包,如果有同学不清楚闭包是什么?闭包怎么用?点击👉这里👈学习一下吧。

⬆ 返回目录

3.3 用 class 实现(更贴近日常写法)

classPermissionManager{staticinstance=null;constructor(){if(PermissionManager.instance){returnPermissionManager.instance;}this.role='guest';this.permissions=[];PermissionManager.instance=this;}check(perm){returnthis.permissions.includes(perm);}setRole(role,perms){this.role=role;this.permissions=perms;}}// 无论怎么 new,都是同一个constpm1=newPermissionManager();constpm2=newPermissionManager();console.log(pm1===pm2);// true

⬆ 返回目录

3.4 实际用法:按钮权限、路由守卫

// permission.js - 全局唯一的权限管理器classPermissionManager{staticinstance=null;constructor(){if(PermissionManager.instance)returnPermissionManager.instance;this.permissions=[];PermissionManager.instance=this;}init(perms){this.permissions=perms;}has(perm){returnthis.permissions.includes(perm);}// 用于 v-if 指令:<button v-if="permission.has('user:delete')">hasPermission(perm){return()=>this.has(perm);}}exportconstpermission=newPermissionManager();// 在路由守卫里用// router.beforeEach((to, from, next) => {// if (to.meta.perm && !permission.has(to.meta.perm)) {// next('/403');// return;// }// next();// });

要点:路由、按钮、接口都用同一个permission实例,权限数据统一维护,避免到处复制逻辑。

⬆ 返回目录

3.5 单例的坑

原因建议
测试时状态残留单例在测试间共享提供reset()或在测试前permission.permissions = []
滥用单例不是全局唯一的东西也做成单例只对真正“全局唯一”的用单例
忘了初始化直接用check但没init在登录成功后统一permission.init(perms)

⬆ 返回目录


四、发布订阅模式:消息通知解耦

4.1 是什么?

发布者发事件,订阅者订阅事件,彼此不直接依赖。发布者不关心谁在监听,订阅者不关心谁在发。

⬆ 返回目录

4.2 核心:EventBus

classEventBus{constructor(){this.events={};// { eventName: [fn1, fn2, ...] }}on(eventName,fn){if(!this.events[eventName]){this.events[eventName]=[];}this.events[eventName].push(fn);}off(eventName,fn){if(!this.events[eventName])return;this.events[eventName]=this.events[eventName].filter(cb=>cb!==fn);}emit(eventName,...args){if(!this.events[eventName])return;this.events[eventName].forEach(fn=>fn(...args));}}

⬆ 返回目录

4.3 实战:消息通知中心

// 通知中心:单例 + 发布订阅classNotificationCenter{staticinstance=null;constructor(){if(NotificationCenter.instance)returnNotificationCenter.instance;this.events={};NotificationCenter.instance=this;}// 订阅某种类型的通知on(type,handler){if(!this.events[type])this.events[type]=[];this.events[type].push(handler);}off(type,handler){if(!this.events[type])return;this.events[type]=this.events[type].filter(h=>h!==handler);}// 发布:触发所有订阅者emit(type,payload){if(!this.events[type])return;this.events[type].forEach(handler=>handler(payload));}}constnotify=newNotificationCenter();// 业务 A:订单成功notify.on('orderSuccess',(orderId)=>{Toast.success(`订单${orderId}创建成功`);// 可能还要更新购物车、统计等});// 业务 B:支付成功notify.on('paymentSuccess',(data)=>{Toast.success('支付成功');router.push('/orders');});// 某处触发notify.emit('orderSuccess','ORD123');

⬆ 返回目录

4.4 完整示例:登录后多处联动

// 用户登录成功后,多个模块要同时反应notify.on('loginSuccess',(user)=>{// 模块 1:更新 header 头像header.updateAvatar(user.avatar);});notify.on('loginSuccess',(user)=>{// 模块 2:拉取用户权限permission.init(user.permissions);});notify.on('loginSuccess',()=>{// 模块 3:刷新待办数量todoBadge.refresh();});// 登录接口成功后,只发一次loginApi().then(user=>{notify.emit('loginSuccess',user);});

发布者只管emit,订阅者各自处理,互不依赖,修改一处不影响其他模块。

⬆ 返回目录

4.5 发布订阅的坑

原因建议
内存泄漏组件销毁后没offbeforeUnmount里统一off
事件名魔法字符串到处写'orderSuccess'易 typo抽成常量EVENTS.ORDER_SUCCESS
过度解耦简单父子通信也用 EventBus能用 props/emit 就用,只在跨层、多对多时用
回调地狱用事件代替 Promise异步流程优先用 async/await,事件只做“通知”

⬆ 返回目录


五、策略模式:表单校验规则可插拔

5.1 是什么?

把不同校验规则封装成独立策略,用配置或映射表选择执行哪个,避免大段if-else

⬆ 返回目录

5.2 没策略时:容易变成“面条码”

// 反面教材:每加一个规则就要改这里functionvalidate(value,rule){if(rule==='required'){returnvalue!==''&&value!=null;}if(rule==='email'){return/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(value);}if(rule==='phone'){return/^1\d{10}$/.test(value);}if(rule==='minLength'){returnvalue.length>=6;}// 越加越多...}

⬆ 返回目录

5.3 用策略重构

// 策略对象:每个规则是独立函数conststrategies={required(value){returnvalue!==''&&value!=null&&String(value).trim()!=='';},email(value){return/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(value);},phone(value){return/^1\d{10}$/.test(value);},minLength(value,min){return(value||'').length>=min;},maxLength(value,max){return(value||'').length<=max;}};// 校验器:根据规则名调用对应策略functionvalidate(value,ruleName,...ruleArgs){constfn=strategies[ruleName];if(!fn)returntrue;// 未知规则默认通过returnfn(value,...ruleArgs);}// 使用validate('','required');// falsevalidate('a@b.com','email');// truevalidate('12345','minLength',6);// false

⬆ 返回目录

5.4 实战:表单校验配置化

// 表单校验配置constformRules={username:[{strategy:'required',message:'用户名不能为空'},{strategy:'minLength',params:[3],message:'至少 3 个字符'}],email:[{strategy:'required',message:'邮箱不能为空'},{strategy:'email',message:'邮箱格式不正确'}],phone:[{strategy:'phone',message:'手机号格式不正确'}]};functionvalidateForm(formData){consterrors={};for(const[field,rules]ofObject.entries(formRules)){for(construleofrules){const{strategy,params=[],message}=rule;constvalue=formData[field];constvalid=validate(value,strategy,...params);if(!valid){errors[field]=message;break;// 一个字段只保留第一个错误}}}return{valid:Object.keys(errors).length===0,errors};}// 使用constresult=validateForm({username:'ab',email:'invalid',phone:'13800138000'});console.log(result);// { valid: false, errors: { username: '至少 3 个字符', email: '邮箱格式不正确' } }

新增字段或规则时,只改配置,不改validate核心逻辑。

⬆ 返回目录

5.5 策略的坑

原因建议
策略与业务混在一起策略里写请求、跳转策略只做“规则判断”,返回 boolean
规则参数传错minLength要数字,传了字符串做参数校验或封装成createMinLength(6)
和单例搞混策略不需要全局唯一策略是无状态的纯函数,不共享实例

⬆ 返回目录


六、三者怎么选?

场景推荐模式理由
全局唯一:权限、配置、通知中心单例避免多处实例、状态不一致
一对多/多对多:登录联动、订单状态发布订阅解耦,扩展新监听者不改原有逻辑
多种规则:表单校验、支付方式、折扣计算策略规则可插拔,易维护和扩展

可以组合用,例如:单例的通知中心 + 发布订阅,或策略模式 + 单例的校验器

⬆ 返回目录


七、小结

模式一句话典型用法
单例全局只一个实例权限管理器、通知中心、全局配置
发布订阅发布事件、订阅处理,彼此解耦消息通知、登录后联动、跨模块通信
策略多种规则封装成可替换策略表单校验、支付方式、折扣规则

记住三点:

  1. 单例只给“真正全局唯一”的东西用,并注意测试时重置。

  2. 发布订阅适合跨模块通知,简单通信优先用 props/emit,用完记得off

  3. 策略模式把规则抽成独立函数,用配置驱动,方便扩展。

设计模式不是炫技,而是让代码更好改、更好测、更少 bug 的工具。先能用、再好用,逐步引入即可。

⬆ 返回目录


🔍 本系列专栏导航

📝 JS 进阶必会点

一、《闭包实战:从原理到防抖・节流・缓存|JS 进阶必会篇》

二、《异步基础:Promise & async/await 实战|JS 进阶必会篇》

三、《常用工具函数:深拷贝・去重・扁平化手写实战|JS 进阶必会篇》

四、《事件循环与宏微任务:log 顺序实战解析|JS 进阶必会篇》

五、《设计模式实战:单例・发布订阅・策略 JS 轻量用法|JS 进阶必会篇》

六、《浏览器存储实战:localStorage/sessionStorage/cookie 用法详解|JS 进阶必会篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

💡 更多内容(TS/Vue/工程化等共47篇)已整理成「全体系总目录」👇,收藏后可一站式学习:

📚 系列总览

想系统学习Vue3中后台开发?收藏这份「全体系指南」,47篇干货从基础到工程化全覆盖:

《前端基础实战:JS/TS与Vue体系化扫盲(47 篇完整目录 + 避坑)》

💡 每篇都配套实战场景+避坑指南,帮你摆脱「面向搜索引擎写代码」的尴尬~

⬆ 返回目录


学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。

后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。

关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。

如果你觉得这篇内容对你有帮助,不妨点赞+收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。

我是 Eugene,你的电子学友,我们下一篇干货见~

http://www.cnnetsun.cn/news/1315927.html

相关文章:

  • 财务数字化——解读118页财务共享建设应标方案【附全文阅读】
  • 快速安装配置Maven
  • 【实时Linux工业PLC解决方案系列】第三十七篇 - 实时Linux PLC内存泄漏检测与防护
  • 非常详细:AI大模型课程|非计算机专业转行人工智能,好就业吗?
  • 【技术解析】中国雪深数据集(1979-2023)的多源卫星传感器融合与质量控制
  • 推荐系统模型演进:从协同过滤到深度学习的技术突破与应用实践
  • C#使用MQTT(二):构建一个带重连与消息处理的健壮客户端
  • Uniapp跨平台WiFi状态检测:从基础获取到实时监听
  • Alibaba Sentinel Dashboard:安全加固之自定义登录凭证实战指南
  • Fastjson 反序列化漏洞攻防博弈:绕过手法演进与防御体系构建
  • 全志D1s开发板:RISC-V架构下的嵌入式视频硬解平台
  • n8n子流程调用避坑指南:从数据库写入到模块化开发实战
  • 从零开始:西门子200SMART安全编程全攻略(含手动/自动切换逻辑详解)
  • 紫微斗数职场指南:从命盘看出最适合你的职业方向(含14主星解析)
  • MySQL迁移中的视图权限管控实践:从粗放授权到精细治理
  • 【leetcode】98.验证二叉搜索树
  • 给我的 QQ 助理换个“最强大脑”:Windows 部署 OpenClaw + 替换模型攻略
  • 精益生产常见误区,90%的企业都踩过坑?
  • 用户态网络缓冲区设计
  • Linux学习笔记1
  • 在matlab上进行基于深度强化学习算法自适应调节PID参数的控制,实现一级倒立摆的起摆和平衡
  • Matlab Simulink下的LLC并网与离网逆变器功能介绍:电流闭环控制并网,电压电流双...
  • 微信官方分账开通对接(技术+流程)指南+第三方分账系统科普
  • 基于GD32F303的便携式教学数字示波器设计
  • Ostrakon-VL-8B实战案例:识别店铺名/厨房违规/货架缺货——零售场景79类细粒度任务演示
  • SENT信号解码实战——从半字节到完整帧的解析指南
  • 立创开源:基于ASRPro与ESP8266的离线智能语音盒子设计与实现
  • Linux系统下Qwen3-TTS的部署与优化
  • Qwen3-ASR-1.7B应用场景:跨境电商客服语音质检系统落地
  • QT 消息提示框的优雅退场:定时关闭与透明度渐变动效实现