JS常用的设计模式

持续更新
JS常用的设计模式以及应用场景


*以下内容为个人简单理解以及摘抄部分网上demo组成,有错误请在下方评论指出?*

# 何谓设计模式


没必要被高大上的名词所吓倒,日常coding中或许一个不了解各种设计模式的程序员可能自己其实已经用到了很多

抛开官方的定义在我看来简单来说就是一个简单的思想被统一为规范,按照这个规范可以写出更优雅可控亦或性能更佳的代码,像是框架的单位

软件设计模式有很多,常规的有23种,本文针对其中常用的几种进行简要介绍

# 设计原则
在列举具体的设计模式之前,我们要先知道设计模式本身的规范是什么,这就是设计原则,主要以下三种:
- 单一职责原则(SRP):一个对象或方法只做一件事情。如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。应该把对象或方法划分成较小的粒度

- 最少知识原则(LKP):一个软件实体应当 尽可能少地与其他实体发生相互作用,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的 相互联系,可以转交给第三方进行处理

- 开放-封闭原则(OCP):软件实体(类、模块、函数)等应该是可以 扩展的,但是不可修改,当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,尽量避免改动程序的源代码,防止影响原系统的稳定



# 从最简单的单体/单例模式开始


### 定义:

单体:一个用来划分命名空间并将一批相关的属性和方法组织在一起的对象

单例:顾名仅可以可以被实例化一次:在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例
*在java中单例的定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供*

### 优点:
- 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例
- 因为类控制了实例化过程,所以类可以灵活更改实例化过程
- 单体可以控制局部变量污染

### 应用场景:
- 可以用单例来划分命名空间: 一些对象我们往往只需要一个,如某些数据的缓存
- 借助单例模式,可以把代码组织的更为一致

#### 最基本的单体模式
直接导出一个方法属性集合的对象
```js
// commonjs 导出
module.exports = {
getSingleton() {
return this
}
}
```
#### 用闭包来实现单例
```js

const Ins1 = (function() {  let instance = null  // 利用闭包特性保证实例私有化  return function(opt) {    if (instance === null) {      instance = this    }    for(let k in opt) {      instance[k] = opt[k]    }    return instance  }
})()复制代码
```
测试:
```js
const i1 = new Ins1({ name: 'i1' })

const i2 = new Ins1({ name: 'i2' })

console.log(i1 === i2) // true

console.log(i1.name) // i2
```
补充:在node中一个文件就是一个独立模块,若在某个js文件中导出一个类: `class T {} export default new T` 之后在其他任何外部文件多次引入其实都是保证了 T 类只被实例化了一次而不会被多次初始化。这是因为node遵循了commonjs的规范,所有文件模块在被引用时都会先去模块系统的缓存中查看这个文件是否存在,如果存在就返回缓存否则才会重新创建一个模块,而这个缓存其实也就限制了模块内脚本的多次初始化

# 策略模式


### 定义:
就是解耦,何为策略解耦: 指的是定义一些列的算法,把他们一个个封装起来,目的就是将算法的使用与算法的实现分离开来。说白了就是以前要很多判断的写法,现在把判断里面的内容抽离开来,变成一个个小的个体。如大量的if else或者switch case判断当需求更改时需要添加和更改判断,这违背了设计模式的对修改关闭,对扩展开放的原则

### 优点:
- 减少`command c & command v`, 提高复用性
- 遵循开闭原则,算法独立易于切换、理解、拓展

### 应用场景:
针对代码多种行为设置大量的条件判断时将每一个行为划分为多个独立的对象。每一个对象被称为一个策略。设置多个这种策略对象,可以改进我们的代码质量,也更好的进行单元测试

#### 最简单的执行
```js
function closure() {
// 定义
const strategies = {
plus10: function(arg) {
return arg + 10
},
plus100: function(arg) {
return arg + 100
}
}
// 执行
return function(plus, base){
return strategies[plus](base);
}
}
const strategy = closure()

console.log(strategy('plus10', 1)) // 11
console.log(strategy('plus100', 1)) // 101
```
#### 对比分析
eg.: 代码情景为超市促销,vip为5折,老客户3折,普通顾客没折,计算最后需要支付的金额

意大利逻辑:

```js
function context (name, type, price) {
if (type === 'vip') {
return price * 0.5
} else if (type === 'vip') {
return price * 0.8
} else {
return price
}
}
```
如果type类型非常多,内部逻辑分别也不只是简单的return一个val,那对后续的维护和测试就是灾难,下面拆分逻辑为独立单元:
``` js
class Vip {
constructor () {
this.discount = 0.5
}
getPrice (price) {
return this.discount * price
}
}

class Old {
constructor () {
this.discount = 0.8
}
getPrice (price) {
return this.discount * price
}
}

class Others {
constructor () {
}
getPrice (price) {
return price
}
}

class Context {
constructor () {
this.name = ''
this.strategy = null
this.price = 0
}
setPrice (name, strategy, price) {
this.name = name
this.strategy = strategy
this.price = price
}
getPrice () {
console.log(this.name, this.strategy.getPrice(this.price), '元')
return this.strategy.getPrice(this.price)
}
}
```
测试:
```js
const seller = new Context
const vip = new Vip
const old = new Old
const other = new Others
seller.setPrice('zs', vip, 1000)
seller.getPrice()
seller.setPrice('ls', old, 1000)
seller.getPrice()
seller.setPrice('ww', other, 1000)
seller.getPrice()
// output:
// zs 500 元
// ls 800 元
// ww 1000 元
```
显然逻辑多而复杂时可以极大提高代码可读性以及减少维护成本

# 代理模式


### 定义:
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
著名的代理模式例子为引用计数(reference counting)指针对象
另外代理模式还可分为:
- 虚拟代理:把一些开销很大的对象,延迟到真正需要它的时候才去创建,当对象在创建前或创建中时,由虚拟代理来扮演对象的替身;对象创建后,代理就会将请求直接委托给对象
- 保护代理:用于控制不同权限的对象对目标对象的访问
- 缓存代理: 缓存代理可以作为一些开销大的运算结果提供暂时的存储,下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果

### 优点:
独立职责归属,便于维护测试

### 应用场景:
比如图片的懒加载,数据缓存等

#### 虚拟代理实现图片懒加载
```js
const imgSet = (() => {
let node = new Image
document.body.append(node)

return function(src) {
node.src = src
}
})()

const proxyImg = (() => {
let _img = new Image

_img.onload = function() {
setTimeout(imgSet, 2000, this.src)
}

return function(src) {
imgSet('https://yphoto.eryufm.cn/upload/assets/jump.gif')
_img.src = src
}
})()
// call
proxyImg(`https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1551174639&di=90b4757f68c9480f78c132c930c1df10&src=http://desk.fd.zol-img.com.cn/t_s960x600c5/g5/M00/02/02/ChMkJ1bKxkmIObywAArTTfACinwAALHjACDZuIACtNl408.jpg`)
```

#### 保护代理
对象a需要给对象c发送信息,为了保证a对c是不可见,可用对象b代理转发
```js
// filter some no use or unneed requestions or data
// A ---> B(proxy) ----> C
const a = {
name: 'a',
send (target, info) {
target.receive(info)
}
}
const c = {
name: 'c',
receive (target, info) {
console.log('c receive ', info, ' from ', target.name)
}
}
const b = {
name: 'b',
receive (info) {
if (info) {
c.receive(this, info)
}
}
}
a.send(b, 'good morning')
a.send(b, '')
a.send(b, 'send again')

// output:
// c receive good morning from b
// c receive send again from b
```
上面表示一个最简单的保护代理

#### 缓存代理
顾名思义就是缓存相关的代理

有一个二级别联动的标签列表,第二级的各有自己所属的多个标签根据第一级的参数来发送指定请求来获取,如果想要达到点击第一级列表迅速展示出相关的第二级标签,我们可以在系统空闲时预先将所有标签全部获取并缓存
```js
// 存储所有标签
let tags

const sendApiGetTags = index => {
// ajax.get('/api', { index })
}
let proxyCache = (async () => {
const allTagsCache = {}
const number = 5
const all = []
const params = {}

for (let index = 0; index < number; index++) {
all.push(sendApiGetTags({
...params,
index
}))
}
const list = await Promise.all(all)

list.forEach((res, i) => allTagsCache[i] = res)

return allTagsCache
})()

let setTags = async index => {
// 缓存中有直接拿
if (proxyCache[index]) {
tags = proxyCache[index]
} else {
// 缓存中没有则重发请求
tags = await sendApiGetTags(index)
}
}
```



# 发布订阅模式


### 定义:
一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。
至于发布订阅模式和观察者模式是不是同一样东西不同的人各有看法

### 优点:
订阅者可以根据自己需求当某种Action被触发时完成自己的调度

### 应用场景:
AngularJs的广播、vue的eventbus等

#### 根据主体构建发布订阅的基类
构造发布者基类
```js
class Publisher {
constructor () {
// 订阅发布者的队列 存储每个订阅者
this.subscribers = []
}
deliver (data) {
// 发布消息 调用订阅者的回调 告知订阅者
this.subscribers.forEach(fn => fn.shot(data))

return this
}
}
```
构造订阅者基类
```js
class Observer {
constructor (call) {
// 传入订阅回调
this.shot = call
}
subscribe (publisher) {
if (!publisher.subscribers.some(v => v.shot === this.shot)) {
console.log('订阅该消息')
// 判断当前订阅者是否订阅
publisher.subscribers.push(this)
}
return this
}
unsubscribe (publisher) {
// 移除当前订阅者
console.log('取消订阅')

publisher.subscribers = publisher.subscribers.filter(v => v.shot !== this.shot)

return this
}
}
```
测试:
```js
const pub = new Publisher
const pub2 = new Publisher
const obs = new Observer(deliver => console.log(deliver))

obs.subscribe(pub) // 订阅该消息
obs.subscribe(pub2) // 订阅该消息

pub.deliver('pub deliver first message') // pub deliver first message
pub2.deliver('pub2 deliver first message') // pub2 deliver first message

obs.unsubscribe(pub) // 取消订阅
pub.deliver('pub deliver second message') //
```

# 装饰者模式


### 定义:
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能

### 优点:
- 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互
- 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展

### 应用场景:
- 需要扩展一个类的功能,或给一个类添加附加职责
- 需要动态的给一个对象添加功能,这些功能可以再动态的撤销
- 不必改动原本的逻辑造成不可知问题

#### 给所有的函数调用添加调用前和调用后的钩子

普通函数:
```js
function fn(msg) {
console.log(msg, ' right now')
}

fn('let go') // lets go right now
```
我们知道JS中所有的函数都是基于父类 `Function` 生成的,所以会继承父类原型的方法,下面我们将函数的钩子挂在父类的原型上即可:
```js
// 执行前
Function.prototype.before = function (call) {
const fn = this
// 返回体本身也是函数所以支持继续调用钩子
return function () {
// 调用钩子,同时参数传递到钩子内
call.apply(this, arguments)
// 调用自身
return fn.apply(this, arguments)
}
}
// 执行后
// 和 before 同理
Function.prototype.after = function (call) {
const fn = this

return function () {
const res = fn.apply(this, arguments)

call.apply(this, arguments)

// 返回自身的返回值
return res
}
}
```
测试:
```js
// 重新包装 fn
function fn(msg) {
console.log(msg, ' right now')
}

const decoratorFn = fn.before(function (msg) {
console.log('when we go,', msg)
}).after(function (msg){
console.log('had to go', msg)
})

decoratorFn('lets go')

// out put:
// when we go, right now
// lets go, right now
// had to go, right now
```

# 职责链(责任链)模式


### 定义:
它是一种链式结构,每个节点都有可能两种操作,要么处理该请求停止该请求操作,要么把请求转发到下一个节点,让下一个节点来处理请求

### 优点:
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了

### 应用场景:
JS 中的事件冒泡(事件委托)就是经典案例

#### 实例分析

部门采购物品不同金额需要走不同职位的流程审批,采购部经理可自主决定1w以内的采购,总经理可以决定10w以内的采购,董事长决定100w以内的采购
下面分别抽象处理者构造基类

责任链调度中心:
```js
class Handler {
constructor() {
this.next = null
}
setNext(_handler) {
this.next = _handler
}
handleRequest(money) {}
}


```

采购部经理:
```js
class CGBHandler extends Handler {
handleRequest(money) {
// 1w
if (money < 10000){
console.log('1w以内,同意')
} else {
console.log('金额太大,只能处理1w以内的采购')
if (this.next) {
this.next.handleRequest(money)
}
}
}
}
```

总经理:
```js
class ZJLHandler extends Handler {
handleRequest(money) {
// 10w
if (money < 100000){
console.log('10w以内,同意')
} else {
console.log('金额太大,只能处理10w以内的采购')
if (this.next) {
this.next.handleRequest(money)
}
}
}
}
```

董事长:
```js
class DSZHandler extends Handler {
handleRequest(money) {
// 100w
if (money >= 100000){
console.log('10万以上的我来处理')
//处理其他逻辑
}
}
}
```

封装客户端接口:
```js
const dispatch = (function client() {
const cgb = new CGBHandler()
const zjl = new ZJLHandler()
const dsz = new DSZHandler()

cgb.setNext(zjl)
zjl.setNext(dsz)

return cgb.handleRequest.bind(cgb)
})()
```
测试:
```js

dispath(800000)
// output:
// 金额太大,只能处理1w以内的采购
// 金额太大,只能处理10w以内的采购
// 10万以上的我来处理

dispath(7000)
// output:
// 1w以内,同意

```
补充:
- 纯的责任链:要求请求在这些对象链中必须被处理,而且一个节点处理对象,要么只处理请求,要么把请求转发给下个节点对象处理

- 不纯的责任链:要求在责任链里不一定会有处理结构,而且一个节点对象,即可以处理部分请求,并把请求再转发下个节点处理

.
.
.
.
.
.
未完待续...?


原文链接:

https://rollawaypoint.github.io/2019/02/24/do%20something/JS%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/



转载于:https://juejin.im/post/5c767d27518825763c6d8f96

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/280094.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在Photoshop中制作双曝光图像

Double exposure images are popular at the moment. Taylor Swift’s Style music video and the True Detective opening theme both used the effect. It’s a technique where two separate photos—typically a portrait and a landscape—are blended together into one …

记一次.NET 某安全生产系统 CPU爆高分析

一&#xff1a;背景 1.讲故事今天是&#x1f40f;的第四天&#xff0c;头终于不巨疼了&#xff0c;写文章已经没什么问题&#xff0c;赶紧爬起来写。这个月初有位朋友找到我&#xff0c;说他的程序出现了CPU爆高&#xff0c;让我帮忙看下怎么回事&#xff0c;简单分析了下有两点…

JDBC 学习笔记(一)—— JDBC 基础

1. 什么是 JDBC JDBC&#xff0c;Java Database Connectivity&#xff08;Java 数据库连接&#xff09;&#xff0c;是一组执行 SQL 语句的 Java API。 JDBC&#xff0c;是 Java SE&#xff08;Java Platform, Standard Edition&#xff09;标准的一部分。 Java 程序可以通过 J…

JavaScript享元模式

JavaScript享元模式 通过两个例子的对比来凸显享元模式的特点&#xff1a;享元模式是一个为了提高性能(空间复杂度)的设计模式&#xff0c;享元模式可以避免大量非常相似类的开销。 第一实例&#xff0c;没有使用享元模式&#xff0c;计算所花费的时间和空间使用程度。 要求为&…

mac屏幕截图_如何在Mac上拍摄屏幕截图

mac屏幕截图On a Mac, you can take screenshots with a few quick keyboard shortcuts. But Mac OS X also includes more powerful screenshot tools, too. Here are some of the many ways you can get a screenshot on OS X. 在Mac上&#xff0c;您可以使用一些快速的键盘快…

实现 .Net 7 下的数据库定时检查

在软件开发过程中&#xff0c;有时候我们需要定时地检查数据库中的数据&#xff0c;并在发现新增数据时触发一个动作。为了实现这个需求&#xff0c;我们在 .Net 7 下进行一次简单的演示。PeriodicTimer .Net 6 中新增了 PeriodicTimer 这个类&#xff0c;它可以用来创建一个定…

新手AS常见问题集锦

开发环境 以前开发android的时候可以使用eclipse&#xff0c;虽然现在也能使用eclipse&#xff0c;但是google已经不再支持使用eclipse开发android了。因为google有了自己的IDE---android studio&#xff0c;这个IDE我自己认为安装的时候比较方便&#xff0c;唯一的缺点就是在下…

js进阶 11-6 jquery如何获取和设置元素的宽高(jquery多方法)

js进阶 11-6 jquery如何获取和设置元素的宽高&#xff08;jquery多方法&#xff09; 一、总结 一句话总结&#xff1a;jquery里面多是方法啊&#xff0c;比如jquery对象的宽高。所以取值是方法&#xff0c;赋值就是方法里面带参数。 1、百度富文本编辑器ueditor如何设置宽高&a…

SparseArray代替HashMap

相信大家都明白&#xff0c;手机软件的开发不同于PC软件的开发&#xff0c;因为手机性能相对有限&#xff0c;内存也有限&#xff0c;所谓“寸土寸金”&#xff0c;可能稍有不慎&#xff0c;就会导致性能的明显降低。Android为了方便开发者&#xff0c;特意在android.util这个包…

也许你曾经读过他的书

我们愿用“能理能文、才华多元”来形容他。因为热爱编程和游戏&#xff0c;所以他将爱好变成了职业&#xff0c;并在这条路上持续奔跑&#xff1b;因为热爱分享&#xff0c;所以他坚持在博客上分享技术观点并出版了关于 Azure、微软游戏栈的书籍&#xff1b;因为热爱挑战&#…

python测试框架数据生成工具最全资源汇总

xUnit frameworks 单元测试框架frameworks 框架unittest - python自带的单元测试库&#xff0c;开箱即用unittest2 - 加强版的单元测试框架&#xff0c;适用于Python 2.7以及后续版本pytest - 成熟且功能强大的单元测试框架plugincompat - pytest的执行及兼容性插件nosetests -…

t30智能插座怎么设置_如何设置ConnectSense智能插座

t30智能插座怎么设置If you like the idea of smart outlets, but wish you had one with more than just one receptacle on it, the ConnectSense Smart Outlet is worth looking into. Here’s how to set it up and instantly get double the fun. 如果您喜欢智能插座的想法…

用链表和数组实现HASH表,几种碰撞冲突解决方法

Hash算法中要解决一个碰撞冲突的办法&#xff0c;后文中描述了几种解决方法。下面代码中用的是链式地址法&#xff0c;就是用链表和数组实现HASH表。 he/*hash table max size*/ #define HASH_TABLE_MAX_SIZE 40/*hash table大小*/ int hash_table_size0;/*.BH----------------…

安卓操作sqlite3,增删改查

创建 layout <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"android:layout_width"match_parent"android:layo…

基于.NetCore开发博客项目 StarBlog - (23) 文章列表接口分页、过滤、搜索、排序

1前言上一篇留的坑&#xff0c;火速补上。在之前的第6篇中&#xff0c;已经有初步介绍&#xff0c;本文做一些补充&#xff0c;已经搞定这部分的同学可以快速跳过&#xff0c;基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表对标准的WebApi来说&#xff0c;分…

如何在Chrome中保存您当前的所有标签,以便以后阅读

Chrome allows you to open tabs from your last browsing session when you open the browser. However, what if you want to save your current set of tabs to re-open at any time? Chrome doesn’t provide a way to do that natively, but there is an easy workaround…

ubuntu 16.04(Windows 10双系统+grub引导)无法进入tt1~tt6(NVIDIA驱动安装相关-黑屏,login loop,分辨率)...

目录 前言回顾最终解决&#xff1a;0.关闭x服务1.禁用nouveau2.加入3.更新4.查找匹配驱动5.选择推荐版本6.等待安装后重启,nvidia-smi查看是否安装成功,或者lsmod | grep nvidia&#xff0c;成功结果如下7.重启x服务8.此时还不能进入图形界面&#xff0c;因为nomodeset还在&…

(备忘)打开office2010总是在配置进度

1、同时按上键盘上面的windows键和R键&#xff0c;出现“运行” 2、输入“regedit”&#xff0c;回车进入注册表 3、点击“HKEY_CURRENT_USER”展开&#xff0c;依次“Software”--“Microsoft”--“Office”--"14.0"--"Word"展开&#xff0c;点击"Op…

java、oracle对CLOB处理

oracle CLOB字段转换位VARCHAR 1.实际上处理CLOB字段的时候&#xff0c;直接TO_CHAR&#xff0c;当长度超过4000的时候&#xff0c;会报错&#xff0c;提示列被截取&#xff1b; CLOB转varchar2&#xff1a;select to_char(CLOB字段) from table 2.直接使用SUBSTR对CLOB字段进行…

android 更改软键盘_如何在Android的Google键盘上更改声音和振动

android 更改软键盘Tactile feedback from a touch screen keyboard is crucial, in my opinion, but I don’t like sounds when I tap keys. You may not be like me—maybe sounds are your thing, but vibration is annoying. Or maybe you dislike both (you rebel!). The…