js - 常用设计模式
零、原链和资料
1.js设计模式
注: 代码均为简洁版,如需了解其他版本写法的优缺点,请移步原文章。
一、单体模式
其思想是在一定的作用域范围内保证一个特定类仅有一个实例,意味着当你第二次使用同一个类创建新对象时,应得到和第一次创建对象完全相同。
var Universe; (function(){ var instance; Universe=function Universe(){ if(instance){ return instance; } instance=this; this.xx="xx"; } })(); var uni = new Universe(); Universe.prototype.a = 1 var uni2 = new Universe(); console.log(uni === uni2) //true console.log(uni.a) //1 console.log(uni2.a) //1 console.log(uni.constructor === Universe); //true
二、工厂模式
批量创建对象,可根据不同要求小范围内调整,就如同工厂一样,零件我可以采购,然后根据要求生产福特/mini/沃尔沃...
demo:
- 公共构造函数 CarMaker
- 名为 factory 的 CarMaker 静态方法来创建 car 对象
function CarMaker() {} // 原型链上 CarMaker.prototype.drive = () => { return `I have ${this.doors} doors`; } // 静态方法 // 枚举子对象 CarMaker.compact = () => { this.doors = 4; } CarMaker.convertible = () => { this.doors = 2; } CarMaker.suv = () => { this.doors = 6; } CarMaker.factory = (type) => { if (typeof CarMaker[type] !== 'function') { throw new ReferenceError('Error') } // 关键句,因为 CarMaker.compact/convertible/suv 是独立的函数,并不在 CarMaker 的继承链上 // 所以这里关联到 CarMaker 的继承链上 if (typeof CarMaker[type].prototype.drive !== 'function') { CarMaker[type].prototype = new CarMaker(); } return new CarMaker[type](); } var corolla = CarMaker.factory('compact'); console.log(corolla.drive()); //I have 4 doors
是不是和 Object.create({...})很像
三、迭代器模式
emmm... 用来迭代复杂数据结构的。
基础的 api 设计:
- next() 下一个
- hasNext() 是否有下一个
- reWind() 重置指针
- current() 返回当前
var agg = (function() { var index = 0; var data = [1, 2, 3, 4, 5, 6]; var length = data.length; return { next: function() { //这里是从第一个数据开始输出 本例中为 1 if (!this.hasNext()) { return null; } var element = data[index]; index++; return element; }, hasNext: function() { return index < length; }, reWind: function() { index = 0; }, current: function() { return data[index]; } } })(); while (agg.hasNext()) { console.log(agg.next()); //1,2,3,4,5,6 } agg.reWind(); //此时重置指针到0
四、装饰者模式
可以在运行时候添加附加功能到对象中,其一个方便特征在于其预期行为的可定制和可配置特性。
demo:
假设在开发一个销售商品的Web应用,每一笔信销售都是一个人新的 sale 对象。该对象“知道”有关项目的价格,并可以通过 getPrice() 方法返回加个。
根据不同情况,可以用额外的功能装饰此对象。
假设客户在魁北克省,买房需要支付联邦税和魁北克省税,则此时需要调用联邦税装饰者和魁北克省税装饰者。
// 实例及要求 var sale=new Sale(100); sale=sale.decorate("fedtax"); //联邦税 sale=sale.decorate("quebec"); //魁北克省税 sale=sale.decorate("miney"); //转为美元格式 sale.getPrice(); //返回价格
function Sale(price) { this.price = price; this.decorateList = []; } // 注意这里是静态方法 Sale.decorators = {}; Sale.decorators.fedtax = { getPrice: function(price) { var price = this.uber.getPrice(); return price * 0.8; //对price进行处理 }, } Sale.decorators.quebec = { getPrice: function(price) { var price = this.uber.getPrice(); return price * 0.7; //对price进行处理 }, } Sale.decorators.money = { getPrice: function(price) { var price = this.uber.getPrice(); return "$" + price * 0.9; //对price进行处理 }, } Sale.prototype.decorate = function(decorator) { this.decorateList.push(decorator); return this; }; Sale.prototype.getPrice = function() { var price = this.price; this.decorateList.forEach(function(name) { price = Sale.decorators[name].getPrice(price); }); return price; }; var sale = new Sale(100); sale = sale.decorate("fedtax"); //联邦税 sale = sale.decorate("quebec"); //魁北克省税 sale = sale.decorate("money"); //转为美元格式 console.log(sale.getPrice()); //$50.4
便于链式调用的改版:
function Sale(price) { this.price = price; this.decorateList = []; } Sale.decorators = {}; // 自定义的装饰者 Sale.decorators.fedTax = { getPrice(price) { return price * 0.8; } } Sale.decorators.queBec = { getPrice(price) { return price * 0.7; } } Sale.decorators.money = { getPrice: function(price) { return "$" + price * 0.9; //对price进行处理 }, } Sale.prototype.decorate = function(decorator) { this.decorateList.push(decorator); return this; // 链式调用 } Sale.prototype.getPrice = function() { let price = this.price; this.decorateList.forEach(name => { price = Sale.decorators[name].getPrice(price); }) return price; } let sale = new Sale(100); sale.decorate('fedTax').decorate('queBec').decorate('money'); console.log(sale.getPrice()); //$50.4
五、策略模式
策略模式支持在运行时候选择算法。
例如用在表单验证问题上,可以创建一个具有 validate() 方法的验证器对象,无论表单具体类型是什么,该方法都会被调用,并且返回结果或者错误信息。 其核心思想是:预先定义各种规则字典(types), 然后定义数据(data)与规则字典的对应关系(config), 接着循环数据的每个字段,执行匹配的规则检验,搜集对应的 success/error 信息。
// 定义验证器对象 let validator = { // 所有可以的验证规则处理类存放的地方,后面会单独定义 types: {}, // 验证类型所对应的错误信息 messages: [], // 当然需要使用的验证类型 config: {}, // 暴露的公开验证方法 // 传入的参数是 key: value 对 validate: function(data) { let i, msg, type, checker, result_ok; // 清空所有的错误信息 this.messages = []; for (i in data) { if (data.hasOwnProperty(i)) { type = this.config[i]; // 根据key查询是否有存在的验证规则 checker = this.types[type]; // 获取验证规则的验证类 if (!type) continue; // 如果验证规则不存在,则不处理 if (!checker) { // 如果验证规则类不存在,抛出异常 throw { name: 'ValidationError', message: 'No handler to validate type ' + type }; } result_ok = checker.validate(data[i]); // 使用查到的单个验证类进行验证 if (!result_ok) { msg = "Invalid value for *" + i + "*, " + checker.instructions; this.messages.push(msg); } } } return this.hasErrors(); }, hasErrors: function() { return this.messages.length !== 0; } } // 然后剩下的工作,就是定义types里存放的各种验证类了 // 验证给定的值是否不为空 validator.types.isNonEmpty = { validate: function(value) { return value !== ''; }, instructions: '传入的值不能为空', }; // 验证给定的值是否是数字 validator.types.isNumber = { validate: function(value) { return !isNaN(value); }, instructions: '传入的值只能是合法的数字,例如:1, 3.14 or 2010', }; // 验证给定的值是否只是字母或数字 validator.types.isAlphaNum = { validate: function(value) { return !/[^a-z0-9]/i.test(value); }, instructions: '传入的值只能保护字母和数字,不能包含特殊字符', } // 使用的时候,我们首先要定义需要验证的数据集合,然后还需要定义每种数据需要验证的规则类型,代码如下: let data = { first_name: 'Tom', last_name: 'Xu', age: 'unknown', username: 'TomXu@', } // 配置下验证规则 validator.config = { first_name: 'isNonEmpty', age: 'isNumber', username: 'isAlphaNum' } // 最后获取验证结果 validator.validate(data); if (validator.hasErrors()) { console.log(validator.messages.join('\n')); }
策略模式可以作为 switch-case 的拓展版本。
六、代理模式
在代理模式中,一个对象充当另外一个对象的接口。
代理模式是介于对象的客户端和对象本身之间,并且对该对象的访问进行保护。
原理是:在 代理对象中实例化原来需要的对象,然后调用相应的方法。
注: 在 es6 中有一个原生的代理对象:Proxy,vue 3.0 也将基于这个代理对象。
demo: 卖家 - 邮局(proxy) - 买家
let Package = function(receiver) { this.receiver = receiver; } let Seller = function(good) { this.package = good; this.send = function(gift) { return good.receiver + '你的包裹' + gift; } } let Express = function(good) { this.package = good; this.send = function(packageName) { return new Seller(good).send(packageName); } } let ems = new Express(new Package('gary')); console.log(ems.send('键盘'));
七、中介者模式
中介者模式可以让多个对象之间松耦合,并降低维护成本。
在中介者模式下,对象与对象之间的联系全靠中介者,者意味着除中介者外,其他对象不知道还有没有另外的的对象,这是中介者模式与代理模式不一样的地方。
function Player(name) { this.point = 0; this.name = name; } Player.prototype.play = function() { this.point += 1; mediator.played(); // mediator 中介 } let scoreBoard = { element: document.querySelector('#score'), // '这里放 dom 元素,用来显示分数' update: function(score) { // 更新分数 let msg = ''; for (let i in score) { if (score.hasOwnProperty(i)) { msg += score[i] + '-'; } } this.element.innerText = msg; }, } let mediator = { // 中介 players: {}, // 存放玩家对象 setUp: function() { let players = this.players; players.home = new Player('home'); players.guest = new Player('guest'); }, played() { let players = this.players; let score = { home: players.home.point, guest: players.guest.point }; scoreBoard.update(score); }, keypress: function(e) { e = e || window.event; if (e.which === 49) { // 按键 1 return mediator.players.home.play(); } if (e.which === 48) { // 按键 0 return mediator.players.guest.play(); } }, } mediator.setUp(); // 启动(初始化) window.onkeypress = mediator.keypress; setTimeout(function() { //设置5秒游戏时间 window.onkeypress = null; alert("game end"); }, 5000);
八、观察者/订阅-发布模式
详情请移步 js - 观察者模式与订阅发布模式
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战