话不多说,这里记录一些常见的设计模式,常看常新,也能提升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。
策略模式就是定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,那么这是比较官方的说法。代入我们这个计算奖金的例子里面来讲的话,每个if语句里面的逻辑就相当于算法,我们要把这些算法用一些手段封装起来,让它们之间独立,保持一个平等的关系也就相当于它们之间可以相互替换

改写策略模式:

首先,策略模式至少由两部分组成,第一个是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个是Context(上下文),Context负责接收请求,之后把请求委托给某一个具体的策略类。

其实很好理解,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版本的策略模式

 上面的策略模式是模仿一些传统的面向对象编程语言的策略模式的实现,而在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))

采用策略模式优点:

  1. 可以有效地避免多重条件选择语句
  2. 代码复用性高,避免了很多粘贴复制的操作。
  3. 策略模式提供了对开放封闭原则的支持,将算法独立封装在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常用设计模式的更多相关文章

  1. 前端JS常用字符串处理实例

    字符串处理常常用在处理服务器回传的数据.动态拼接生成html等,是前端面试的必考题. 我觉得字符串处理这种常用到的,一定要了然于心,不然用到时急急忙忙去翻手册费半天. 入正题,首先提出平常遇到的几个需 ...

  2. 前端js常用正则表达式实例讲解

    本文内容整理自他人优秀的博客,非纯原创.仅借此学习和整理. 1.匹配用户名 规则描述: 长度4-6位: {4,16} 字母: [a-z] [A-Z] 数字: [0-9] 下划线: [_] 减号: [- ...

  3. js常用设计模式实现(一)单例模式

    前言 什么是设计模式 设计模式是一种能够被反复使用,符合面向对象特性的代码设计经验的总结,合理的使用设计模式能够让你得代码更容易维护和可靠 设计模式的类型共分为创建型模式,结构型模式,行为型模式三种 ...

  4. js常用设计模式

    组合使用构造函数模式和原型模式.其中,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性. 例子: <script> function Person(name,age,job) ...

  5. js常用设计模式实现(三)建造者模式

    创建型模式 创建型模式是对一个类的实例化过程进行了抽象,把对象的创建和对象的使用进行了分离 关于创建型模式,已经接近尾声了,还剩下建造者模式和原型模式,这一篇说一说建造者模式 建造者模式的定义 将一个 ...

  6. js/jquery/html前端开发常用到代码片段

    1.IE条件注释 条件注释简介 IE中的条件注释(Conditional comments)对IE的版本和IE非IE有优秀的区分能力,是WEB设计中常用的hack方法.条件注释只能用于IE5以上,IE ...

  7. 闲聊——浅谈前端js模块化演变

    function时代 前端这几年发展太快了,我学习的速度都跟不上演变的速度了(门派太多了,后台都是大牛公司支撑类似于facebook的react.google的angular,angular的1.0还 ...

  8. 7 种 Javascript 常用设计模式学习笔记

    7 种 Javascript 常用设计模式学习笔记 由于 JS 或者前端的场景限制,并不是 23 种设计模式都常用. 有的是没有使用场景,有的模式使用场景非常少,所以只是列举 7 个常见的模式 本文的 ...

  9. 前端Demo常用库文件链接

    <!doctype html><html><head> <meta charset="UTF-8"> <title>前端 ...

  10. 前端js的书写规范和高效维护的方案_自我总结使用的方案

    作为程序员,人生最值得幸福的事有几件: 解决困扰了很长时间的问题 升职加薪 找个漂亮又靠谱的对象 深得领导的喜欢 带领团队冲锋陷阵 ... 哈哈,这些都是梦想,暂时想想就好了.这肯定和我说的东西不符合 ...

随机推荐

  1. ChainofResponsibility Pattern

    责任链模式: 参考:https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html 避免请求发送者与接收者耦合在一 ...

  2. [vsCode]Visual Studio Code左侧导航栏图标丢失

    人在摸鱼,闲着没事看了看没用过的git窗口,然后右键把导航栏快捷图标关了 打开设置(Ctrl+,),搜索sidebar 编辑 settings.json { "window.zoomLeve ...

  3. MQTT 客户端出现连接订阅等问题时如何排查?

    大家好,这是一期社区专题 FAQ.我们整理了近期社区中关注度较高的问题,在这里进行统一汇总解答. 今后本系列内容将不定期推送,敬请关注. 同时,如果大家在使用 EMQX 的过程中遇到问题,欢迎通过以下 ...

  4. oracle之如何获取执行计划方法

    1.什么是执行计划 为了执行sql语句,Oracle在内部必须实现许多步骤,这些步骤可能是从数据库中物理检索数据行,或者用某种方法来准备数据行等,接着Oracle会按照一定的顺序一次执行这些步骤,最后 ...

  5. DataTable中排序的开启与禁用

    1. 2. orderable设置成true会打开排序功能,设置为false会禁用排序功能.

  6. 使用Github或Gitlab的Webhooks实现代码自动更新部署(Ubuntu20.04)

    1.安装ssh服务root@Ubuntu:~# apt-get install openssh-server 2.部署phproot@Ubuntu:~# add-apt-repository ppa: ...

  7. JS实现打字效果(_会闪烁)

    背景 更新博客园个人博客时,突发奇想想要将子标题的入场特效做成类似Linux命令行输命令时的样式 效果网页:LanceEst - 博客园 (cnblogs.com) 思路和代码 1 <h2 id ...

  8. (K8s学习笔记五)Pod的使用详解

    1.Pod用法 K8s里使用的容器不能使用启动命令是后台执行程序,如:nohup ./start.sh &,该脚本运行完成后kubelet会认为该Pod执行结束,将立刻销毁该Pod,如果该Po ...

  9. .net中微信、支付宝回调

    需求:自助机调用接口生成二维码,用户扫描二维码付款后,回调方法里写入到数据库,自助机轮询查数据库判断用户是否付款. 1 using bk.Services.Log; 2 using bk.web.Co ...

  10. padding&margin

    margin是盒子的外边距,即盒子与盒子之间的距离,而padding是内边距,是盒子的边与盒子内部元素的距离. 鞋盒里面的鞋到 盒子的距离设置为 padding ,而 鞋盒到鞋架的距离设置为margi ...