前端JS常用设计模式
话不多说,这里记录一些常见的设计模式,常看常新,也能提升JavaScript编程水平
一、设计原则
二、单例模式
单例模式的定义是,保证一个类仅有一个实例,并且要提供访问他的全局api
单例模式在前端是一种很常见的模式,一些对象我们往往就只需要一个,如VueX,React-redux等框架全局状态管理工具
1.Class语法简单实现
class Singleton {
constructor (name) {
this.name = name
}
// 静态方法
static getInstance (name) {
if (!this.instance) {
this.instance = new Singleton(name)
}
return this.instance
}
}
let a = Singleton.getInstance('a1')
let b = Singleton.getInstance('b2')
console.log(a == b)
2.闭包结合new关键字
var instance
function Singleton (name) {
if (!instance) {
return (instance = this)
}
return instance
}
let a = new Singleton('a1')
let b = new Singleton('b1')
console.log(a===b); // true
3.代理模式创建单例
// 定义代理
var CreateSingleton = (function () {
let instance;
// 接收一个普通类用于创建单例特性
return function(Singleton,name) {
if(!instance) {
return instance = new Singleton(name)
}
return instance
}
})() var Singleton = function(name) {
this.name = name
} let a = new CreateSingleton(Singleton,'a')
let b = new CreateSingleton(Singleton, 'b')
console.log(a===b); // true
- 代理只专注创建单例
- 让Singleton变成普通类
- 通过代理让Singleton普通类拥有单例特性
实际应用:如页面弹窗
惰性单例:
var createDiv = (function () {
var instance
return function () {
if (!instance) {
var div = document.createElement('div')
div.innerHTML = '我是登录窗口'
div.style.display = 'none'
document.body.appendChild(div)
return instance = div
}
return instance
}
})() button.addEventListener('click', function () {
let div = createDiv()
div.style.display = 'block'
})
三、观察者模式
1.被观察者
// 被观察者类
class Subject {
//未传值初始为空
constructor(state = "") {
// 初始状态
this.state = state
// 观察者方法队列
this.observsers = []
}
// 设置自己的状态
setState(val) {
// 告诉观察者目前改变成了什么状态
this.state = val;
// 同时需要把自己身上的观察者方法全部触发
this.observsers.map(item => {
// item是每一个观察者,每一个观察者是一个对象
item.callback(this.state);
})
}
// 添加观察者
addObserver(observer) {
// 把观察者传递进来
this.observsers.push(observer)
}
// 删除观察者
removeObserver(observer) {
// 过滤出来非当前观察者的观察者
this.observsers = this.observsers.filter(obs => obs !== observer);
}
}
2.观察者
// 观察者类
class Observer {
// name需要观察的参数
// callback 观察的参数达到边界条件触发的事件
constructor(name, callback = () => { }) {
this.name = name;
this.callback = callback;
}
}
3.使用
初始数据
let obj = {
name: "abc"
}
创建观察者与被观察者
// 创建观察者
const ObserverName = new Observer('监听obj改变', () => { console.log('obj发生改变'); })
// 创建一个被观察者
const name = new Subject(obj.name)
// 添加一个观察者
name.addObserver(ObserverName)
//触发观察者方法
name.setState('123')
四、发布-订阅模式
模拟报纸的订阅与发布过程:
// 报社
class Publisher {
constructor(name, channel) {
this.name = name;
this.channel = channel;
}
// 注册报纸
addTopic(topicName) {
this.channel.addTopic(topicName);
}
// 推送报纸
publish(topicName) {
this.channel.publish(topicName);
}
}
// 订阅者
class Subscriber {
constructor(name, channel) {
this.name = name;
this.channel = channel;
}
//订阅报纸
subscribe(topicName) {
this.channel.subscribeTopic(topicName, this);
}
//取消订阅
unSubscribe(topicName) {
this.channel.unSubscribeTopic(topicName, this);
}
//接收推送
update(topic) {
console.log(`${topic}已经送到${this.name}家了`);
}
}
// 第三方平台
class Channel {
constructor() {
this.topics = {};
}
//报社在平台注册报纸
addTopic(topicName) {
this.topics[topicName] = [];
}
//报社取消注册
removeTopic(topicName) {
delete this.topics[topicName];
}
//订阅者订阅报纸
subscribeTopic(topicName, sub) {
if (this.topics[topicName]) {
this.topics[topicName].push(sub);
}
}
//订阅者取消订阅
unSubscribeTopic(topicName, sub) {
this.topics[topicName].forEach((item, index) => {
if (item === sub) {
this.topics[topicName].splice(index, 1);
}
});
}
//平台通知某个报纸下所有订阅者
publish(topicName) {
this.topics[topicName].forEach((item) => {
item.update(topicName);
});
}
}
这里的报社我们可以理解为发布者(Publisher)的角色,订报纸的读者理解为订阅者(Subscriber),第三方平台就是事件中心;报社在平台上注册某一类型的报纸,然后读者就可以在平台订阅这种报纸;三个类准备好了,我们来看下他们彼此如何进行联系:
var channel = new Channel(); var pub1 = new Publisher("报社1", channel);
var pub2 = new Publisher("报社2", channel); pub1.addTopic("晨报1");
pub1.addTopic("晚报1");
pub2.addTopic("晨报2"); var sub1 = new Subscriber("小明", channel);
var sub2 = new Subscriber("小红", channel);
var sub3 = new Subscriber("小张", channel); sub1.subscribe("晨报1");
sub2.subscribe("晨报1");
sub2.subscribe("晨报2");
sub3.subscribe("晚报1"); sub3.subscribe("晨报2");
sub3.unSubscribe("晨报2"); pub1.publish("晨报1");
pub1.publish("晚报1");
pub2.publish("晨报2"); //晨报1已经送到小明家了
//晨报1已经送到小红家了
//晚报1已经送到小张家了
//晨报2已经送到小红家了
我们先定义了一个调度中心channel,然后分别定义了两个报社pub1、pub2,以及三个读者sub1、sub2和sub3;两家报社在平台注册了晨报1、晚报1和晨报2三种类型的报纸,三个读者各自订阅各家的报纸,也能取消订阅。
我们可以发现在发布者中并没有直接维护订阅者列表,而是注册了一个事件主题,这里的报纸类型相当于一个事件主题;订阅者订阅主题,发布者推送某个主题时,订阅该主题的所有读者都会被通知到;这样就避免了观察者模式无法进行过滤筛选的缺陷。
观察者模式与发布-订阅模式的区别
观察者模式是一种紧耦合的状态,发布/订阅模式相比于观察者模式多了一个中间媒介,因为这个中间媒介,发布者和订阅者的关联为松耦合
- 通知订阅者的方式不同
- 内部维护的内容不同
观察者模式把观察者对象维护在目标对象中的,需要发布消息时直接发消息给观察者。在观察者模式中,目标对象本身是知道观察者存在的。发布/订阅模式中,发布者并不维护订阅者,也不知道订阅者的存在,所以也不会直接通知订阅者,而是通知调度中心,由调度中心通知订阅者。
Vue的基本原理即运用发布-订阅模式,具体可查看手写vue2.x原理:https://gitee.com/younghxp/vue2-data-response
五、策略模式
策略模式的目的就是使算法的使用与算法分离开来
例子:不同的输入需要产生不同的输出,比如现在需要根据员工的等级来发最后的年终奖,这时候最简便的方法莫过于写一大堆 if···else···
语句了。就像这样:
var calculateBonus = function( level, salary ){
if ( level === 'S' ){
return salary * 4;
}
if ( level === 'A' ){
return salary * 3;
}
if ( level === 'B' ){
return salary * 2;
}
};
calculateBonus( 'B', 20000 ); // 输出:40000
calculateBonus( 'S', 6000 ); // 输出:24000
缺点明显:
- 整个calculateBouns函数太庞大了,包含了非常多的
if-else
语句,这些语句需要覆盖所有的语句。 - 违背了开放封闭原则。如果需要临时增加员工等级或者修改分配方式,那么只能直接去修改原来的代码,增加判断条件,这种侵入是很大的。
- 重用性低,这样的代码无法高效复用,只能无脑CV。
改写策略模式:
其实很好理解,Context就相当于一个中转站,接收不同的计算请求,然后把具体的计算任务转发给某一个具体策略类
1.定义一组策略类
let Bouns = function () {
this.salary = null;
this.strategy = null
}
Bouns.prototype.setSalary = function (salary) {
// 设置员工的原始工资
this.salary = salary;
}
Bouns.prototype.setStrategy = function (strategy) {
// 设置策略对象
this.strategy = strategy;
}
Bouns.prototype.getBouns = function () {
return this.strategy.calculate(this.salary);
}
2.实现中转站
let Bouns = function () {
this.salary = null;
this.strategy = null
}
Bouns.prototype.setSalary = function (salary) {
// 设置员工的原始工资
this.salary = salary;
}
Bouns.prototype.setStrategy = function (strategy) {
// 设置策略对象
this.strategy = strategy;
}
Bouns.prototype.getBouns = function () {
return this.strategy.calculate(this.salary);
}
3.实现策略模式年终奖计算
let bouns = new Bouns()
bouns.setSalary(20000);
bouns.setStrategy(new LevelS())
console.log(bouns.getBouns()) // 输出: 80000
JavaScript版本的策略模式
1.创建策略对象
let strategies = {
'S': function (salary) {
return salary * 4
},
'A': function (salary) {
return salary * 3
},
'B': function (salary) {
return salary * 2
},
}
2.创建一个计算函数
function calculateBouns (level, salary) {
return strategies[level](salary);
}
console.log(calculateBouns('S', 20000))
采用策略模式优点:
- 可以有效地避免多重条件选择语句
- 代码复用性高,避免了很多粘贴复制的操作。
- 策略模式提供了对开放封闭原则的支持,将算法独立封装在strategies中,使得它们易于切换,易于扩展。
六、代理模式
为一个对象提供一个代用品或占位符,以便控制对它的访问;
该模式场景需要三类角色,分别为使用者、目标对象和代理者,使用者的目的是直接访问目标对象,但却不能直接访问,而是要先通过代理者。因此该模式非常像明星代理人的场景。其特征为:
- 使用者无权访问目标对象;
- 中间加代理,通过代理做授权和控制
在js中常用到的是缓存代理和虚拟代理
1.虚拟代理
例子:图片懒加载场景
不使用代理模式:
// 不使用代理的预加载图片函数如下
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
var img = new Image();
img.onload = function(){
imgNode.src = this.src;
};
return {
setSrc: function(src) {
imgNode.src = "loading.gif";
img.src = src;
}
}
})();
// 调用方式
myImage.setSrc("pic.png");
缺点:
- 代码耦合度比较大,一个函数内负责做了几件事情。未满足面向对象设计原则中单一职责原则;
- 当某个时候不需要图片预加载的时候,需要从myImage 函数内把代码删掉,这样代码耦合性太高。
使用代理模式:
var myImage = (function(){
var imgNode = document.createElement("img");
document.body.appendChild(imgNode);
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
// 代理模式
var ProxyImage = (function(){
var img = new Image();
img.onload = function(){
myImage.setSrc(this.src);
};
return {
setSrc: function(src) {
myImage.setSrc("loading.gif");
img.src = src;
}
}
})();
// 调用方式
ProxyImage.setSrc("pic.png");
优点:
- myImage 函数只负责做一件事。创建img元素加入到页面中,其中的加载loading图片交给代理函数ProxyImage 去做。
- 加载成功以后,代理函数ProxyImage 会通知及执行myImage 函数的方法。
- 当以后不需要代理对象的话,我们直接可以调用本体对象的方法即可
2.缓存代理
var mult = function(){
var a = 1;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a = a*arguments[i];
}
return a;
};
// 计算加法
var plus = function(){
var a = 0;
for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
a += arguments[i];
}
return a;
}
// 代理函数
var proxyFunc = function(fn) {
var cache = {}; // 缓存对象
return function(){
var args = Array.prototype.join.call(arguments,',');
if(args in cache) {
return cache[args]; // 使用缓存代理
}
return cache[args] = fn.apply(this,arguments);
}
};
var proxyMult = proxyFunc(mult);
console.log(proxyMult(1,2,3,4)); // 24
console.log(proxyMult(1,2,3,4)); // 缓存取 24 var proxyPlus = proxyFunc(plus);
console.log(proxyPlus(1,2,3,4)); // 10
console.log(proxyPlus(1,2,3,4)); // 缓存取 10
实际应用:HTML元素事件代理;ES6 Proxy等
七、装饰器模式
装饰器,顾名思义,就是在原来方法的基础上去装饰一些针对特别场景所适用的方法,即添加一些新功能。因此其特征主要有两点:
- 为对象添加新功能;
- 不改变其原有的结构和功能,即原有功能还继续会用,且场景不会改变。
有点类似代理模式
class Circle {
draw() {
console.log('画一个圆形');
}
} class Decorator {
constructor(circle) {
this.circle = circle;
}
draw() {
this.circle.draw();
this.setRedBorder(circle);
}
setRedBorder(circle) {
console.log('画一个红色边框');
}
} let circle = new Circle();
let decorator = new Decorator(circle);
decorator.draw(); //画一个圆形,画一个红色边框
装饰器模式,让对象更加稳定,且易于复用。而不稳定的功能,则可以在个性化定制时进行动态添加。
实际使用:ajax请求的拦截器等
前端JS常用设计模式的更多相关文章
- 前端JS常用字符串处理实例
字符串处理常常用在处理服务器回传的数据.动态拼接生成html等,是前端面试的必考题. 我觉得字符串处理这种常用到的,一定要了然于心,不然用到时急急忙忙去翻手册费半天. 入正题,首先提出平常遇到的几个需 ...
- 前端js常用正则表达式实例讲解
本文内容整理自他人优秀的博客,非纯原创.仅借此学习和整理. 1.匹配用户名 规则描述: 长度4-6位: {4,16} 字母: [a-z] [A-Z] 数字: [0-9] 下划线: [_] 减号: [- ...
- js常用设计模式实现(一)单例模式
前言 什么是设计模式 设计模式是一种能够被反复使用,符合面向对象特性的代码设计经验的总结,合理的使用设计模式能够让你得代码更容易维护和可靠 设计模式的类型共分为创建型模式,结构型模式,行为型模式三种 ...
- js常用设计模式
组合使用构造函数模式和原型模式.其中,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性. 例子: <script> function Person(name,age,job) ...
- js常用设计模式实现(三)建造者模式
创建型模式 创建型模式是对一个类的实例化过程进行了抽象,把对象的创建和对象的使用进行了分离 关于创建型模式,已经接近尾声了,还剩下建造者模式和原型模式,这一篇说一说建造者模式 建造者模式的定义 将一个 ...
- js/jquery/html前端开发常用到代码片段
1.IE条件注释 条件注释简介 IE中的条件注释(Conditional comments)对IE的版本和IE非IE有优秀的区分能力,是WEB设计中常用的hack方法.条件注释只能用于IE5以上,IE ...
- 闲聊——浅谈前端js模块化演变
function时代 前端这几年发展太快了,我学习的速度都跟不上演变的速度了(门派太多了,后台都是大牛公司支撑类似于facebook的react.google的angular,angular的1.0还 ...
- 7 种 Javascript 常用设计模式学习笔记
7 种 Javascript 常用设计模式学习笔记 由于 JS 或者前端的场景限制,并不是 23 种设计模式都常用. 有的是没有使用场景,有的模式使用场景非常少,所以只是列举 7 个常见的模式 本文的 ...
- 前端Demo常用库文件链接
<!doctype html><html><head> <meta charset="UTF-8"> <title>前端 ...
- 前端js的书写规范和高效维护的方案_自我总结使用的方案
作为程序员,人生最值得幸福的事有几件: 解决困扰了很长时间的问题 升职加薪 找个漂亮又靠谱的对象 深得领导的喜欢 带领团队冲锋陷阵 ... 哈哈,这些都是梦想,暂时想想就好了.这肯定和我说的东西不符合 ...
随机推荐
- ChainofResponsibility Pattern
责任链模式: 参考:https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html 避免请求发送者与接收者耦合在一 ...
- [vsCode]Visual Studio Code左侧导航栏图标丢失
人在摸鱼,闲着没事看了看没用过的git窗口,然后右键把导航栏快捷图标关了 打开设置(Ctrl+,),搜索sidebar 编辑 settings.json { "window.zoomLeve ...
- MQTT 客户端出现连接订阅等问题时如何排查?
大家好,这是一期社区专题 FAQ.我们整理了近期社区中关注度较高的问题,在这里进行统一汇总解答. 今后本系列内容将不定期推送,敬请关注. 同时,如果大家在使用 EMQX 的过程中遇到问题,欢迎通过以下 ...
- oracle之如何获取执行计划方法
1.什么是执行计划 为了执行sql语句,Oracle在内部必须实现许多步骤,这些步骤可能是从数据库中物理检索数据行,或者用某种方法来准备数据行等,接着Oracle会按照一定的顺序一次执行这些步骤,最后 ...
- DataTable中排序的开启与禁用
1. 2. orderable设置成true会打开排序功能,设置为false会禁用排序功能.
- 使用Github或Gitlab的Webhooks实现代码自动更新部署(Ubuntu20.04)
1.安装ssh服务root@Ubuntu:~# apt-get install openssh-server 2.部署phproot@Ubuntu:~# add-apt-repository ppa: ...
- JS实现打字效果(_会闪烁)
背景 更新博客园个人博客时,突发奇想想要将子标题的入场特效做成类似Linux命令行输命令时的样式 效果网页:LanceEst - 博客园 (cnblogs.com) 思路和代码 1 <h2 id ...
- (K8s学习笔记五)Pod的使用详解
1.Pod用法 K8s里使用的容器不能使用启动命令是后台执行程序,如:nohup ./start.sh &,该脚本运行完成后kubelet会认为该Pod执行结束,将立刻销毁该Pod,如果该Po ...
- .net中微信、支付宝回调
需求:自助机调用接口生成二维码,用户扫描二维码付款后,回调方法里写入到数据库,自助机轮询查数据库判断用户是否付款. 1 using bk.Services.Log; 2 using bk.web.Co ...
- padding&margin
margin是盒子的外边距,即盒子与盒子之间的距离,而padding是内边距,是盒子的边与盒子内部元素的距离. 鞋盒里面的鞋到 盒子的距离设置为 padding ,而 鞋盒到鞋架的距离设置为margi ...