目录

前端常用的设计模式

前端常用的设计模式

什么是设计模式

在软件工程中,设计模式是对软件设计中普遍存在的各种问题提出的解决方案;

1. 工厂模式

1.1 概念

工程模式是不暴露对象的具体逻辑,而是将逻辑封装在一个函数中;像工厂一样重复产生类型的产品,工程模式只需要我们传入正确的参数,就能生产类似的产品。

1.2 实现

工厂模式根据抽象程度的不能分为:

  1. 简单工厂模式
  2. 工厂方法模式
  3. 抽象工程模式
1.2.1 简单工厂模式

简单工程模式也叫静态工程模式,用一个工厂对象创建同一个对象类的实例

function Factory(career) {
	 function User(career, work) {
		 this.career = career
		 this.work = work
	 }
	 let work
	 switch(career) {
		 case 'coder':
		 work = [' ', ' Bug']
		 return new User(career, work)
	 break
	 case 'hr':
		 work = [' ', ' ']
		 return new User(career, work)
	 break
		 case 'driver':
		 work = [' ']
		 return new User(career, work)
		 break
	 case 'boss':
		 work = [' ', ' ', ' ']
		 return new User(career, work)
		 break
	 } 
}
let coder = new Factory('coder')
let boss = new Factory('boss')

Factory就是一个简单工厂,当我们调用工厂函数时,只需要传递name、age、career就可以获取到包含用户工作内容的实例对象。

1.2.2 工厂方法模式

工厂方法模式跟简单工厂模式差不多,但是把具体的产品放到了工厂函数的prototype中。这样一来,扩展产品种类就不必修改工厂函数,和心累就变成抽象类,也可以随时重写某种具体的产品。也就是相当于工厂总部不生产产品了,交给下辖分工厂进行生产;但是进入工厂之前,需要有个判断来验证你要生产的东西是否属于我们的工厂所生产范围,如果是,就丢给下辖工厂来进行生产

// 工厂方法
function Factory(career){
	 if(this instanceof Factory){
		 var a = new this[career]();
		 return a;
	 }else{
		 return new Factory(career);
	 } 
}
// 
Factory.prototype={
	 'coder': function(){
		 this.careerName = ' '
		 this.work = [' ', ' Bug']
	 },
	 'hr': function(){
		 this.careerName = 'HR'
		 this.work = [' ', ' ']
	 },
	 'driver': function () {
		 this.careerName = ' '
		 this.work = [' ']
	 },
	 'boss': function(){
		 this.careerName = ' '
		 this.work = [' ', ' ', ' ']
	 } 
}
let coder = new Factory('coder')
let hr = new Factory('hr')

工厂方法核心代码是工厂里面的判断this是否属于工厂,也就是做了分支判断,这个工厂只做我能做的产品

2.3.3 抽象工程模式

简单工厂模式和工厂方法模式都是直接生产实例,但是抽象工程模式不同,抽象工厂模式并不直接生产实例,而是用于对产品类族的创建

简单来的说就是:简单工厂和方法工厂模式是生产产品,那么抽象工厂模式的工作就是生产工厂的

let CareerAbstractFactory = function(subType, superType) {
	 // 判断抽象工厂是否有该抽象类
	 if (typeof CareerAbstractFactory[superType] === 'function') {
		 // 缓存类
		 function F() {}
		 // 继承父类属性和方法
		 F.prototype = new CareerAbstractFactory[superType]()
		 // 将子类constructor指向父类
		 subType.constructor = subType;
		 // 子类原型继承父类
		 subType.prototype = new F()
	 } else {
		 throw new Error('抽象类不存在')
	 } 
 }

上面代码中CareerAbstractFactory 就是一个抽象工厂方法,该方法在参数中传递子类和父类,在方法内部实现了子类对父类的继承。

2. 单例模式

创建型模式,提供了一个创建对象的最佳方式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建

在应用程序运行期间,单例模式只会在全局作用域下创建一次实例对象,让所有需要调用的地方共享这一单例对象

// 定义一个类
function Singleton(name) {
	 this.name = name;
	 this.instance = null;
}
// 原型扩展类的一个方法getName()
Singleton.prototype.getName = function() {
	 console.log(this.name) 
};
// 获取类的实例
Singleton.getInstance = function(name) {
	 if(!this.instance) {
		 this.instance = new Singleton(name);
	 }
	 return this.instance
};
// 获取对象1
const a = Singleton.getInstance('a');
// 获取对象2
const b = Singleton.getInstance('b');
// 进行比较
console.log(a === b);

3. 策略模式

策略模式指是先定义一系列算法,把他们一个个封装起来,将不变的部分和变化的部分隔开

一个基于策略模式的程序至少有两部分组成

1、策略类,策略类封装了具体的算法,并负责具体的计算过程

2、环境类Context,Context接受客户的请求,随后把请求委婉给一个策略类

var calculateBouns = function(salary,level) {
	 if(level === 'A') {
		 return salary * 4;
	 }
	 if(level === 'B') {
		 return salary * 3;
	 }
	 if(level === 'C') {
		 return salary * 2;
	 } 
};
// 调用如下:
console.log(calculateBouns(4000,'A')); // 16000
console.log(calculateBouns(2500,'B')); // 7500

从上述可有看到,函数内部包含过多的if…else,并且后续改正的时候,需要在函数内部添加逻辑,违反了开放封闭原则

使用策略模式,就是先定义一系列算法,把他们一个个封装起来,将不变的部分和变化的部分隔开

var obj = {
	 "A": function(salary) {
		 return salary * 4;
	 },
	 "B" : function(salary) {
		 return salary * 3;
	 },
	 "C" : function(salary) {
		 return salary * 2;
	 } 
};
var calculateBouns = function(level,salary) {
  return obj[level](salary); 
};
console.log(calculateBouns('A',10000)); // 40000

4. 观察者模式

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知,并自动更新

观察者模式属于行为型模式,关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯

  1. 被观察者模式
class Subject {
	 constructor() {
		 this.observerList = [];
	 }
	 addObserver(observer) {
		 this.observerList.push(observer);
	 }
	 removeObserver(observer) {
		 const index = this.observerList.findIndex(o => o.name === observer.nam
		e);
		 this.observerList.splice(index, 1);
	 }
	 notifyObservers(message) {
		 const observers = this.observeList;
		 observers.forEach(observer => observer.notified(message));
	 }
 }
  1. 观察者
class Observer {
	 constructor(name, subject) {
	 this.name = name;
	 if (subject) {
		 subject.addObserver(this);
	 }
 }
 notified(message) {
	 console.log(this.name, 'got message', message);
 }
}
  1. 使用代码:
const subject = new Subject();
const observerA = new Observer('observerA', subject);
const observerB = new Observer('observerB');
subject.addObserver(observerB);
subject.notifyObservers('Hello from subject');
subject.removeObserver(observerA);
subject.notifyObservers('Hello again');

观察者主动申请加入被观察者的列表,被观察者主动将观察者加入列表

5. 发布订阅模式

发布-订阅是一种消息范式,消息的发送者(发布者)不会将消息直接发送给特定的接受者(订阅者)。而是将发布的消息分为不同的类别,无须了解那些订阅者可能存在,同样,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无须了解那些发布者存在

class PubSub {
 constructor() {
	 this.messages = {};
	 this.listeners = {};
 }
 // 添加发布者
 publish(type, content) {
	 const existContent = this.messages[type];
	 if (!existContent) {
		 this.messages[type] = [];
	 }
	 this.messages[type].push(content);
 }
 // 添加订阅者
 subscribe(type, cb) {
	 const existListener = this.listeners[type];
	 if (!existListener) {
		 this.listeners[type] = [];
	 }
	 this.listeners[type].push(cb);
 }
 // 通知
 notify(type) {
	 const messages = this.messages[type];
	 const subscribers = this.listeners[type] || [];
	 subscribers.forEach((cb, index) => cb(messages[index]));} 
}

发布者代码

class Publisher {
	 constructor(name, context) {
		 this.name = name;
		 this.context = context;
	 }
	 publish(type, content) {
		 this.context.publish(type, content);
	 } 
}

订阅者代码

class Subscriber {
 constructor(name, context) {
	 this.name = name;
	 this.context = context;
 }
 subscribe(type, cb) {
	 this.context.subscribe(type, cb);
 }
}

使用代码:

const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';
const pubsub = new PubSub();
const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');
const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res => {
 console.log('subscriberA received', res) });
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res => {
 console.log('subscriberB received', res) });
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res => {
 console.log('subscriberC received', res) });
pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);

发布者和订阅者需要通过发布订阅中心进行关联,发布者的发布动作和订阅者的订阅动作相互独立,消息派发由发布订阅中心负责

发布订阅模式余观察者模式区别
  1. 在观察者模式中,观察者是知道被观察者的,被观察者一直保持对观察者进行记录,然后在发布订阅模式中,发布者和订阅者不知道对方的存在。他们只有通过消息代理进行通信
  2. 在发布订阅中,组件是松散耦合的,正好和观察者相反
  3. 观察者模式大多数时候是同步的,不如当事件触发,被观察就会去调用观察者的方法。而发布-订阅模式大多数是异步的(使用消息队列)

6.代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要时,提供一个替身对象来控制这个对象的访问,客户实际上访问的是替身对象

1. 缓存代理

缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前端存储的运算结果

实现一个求积的函数

var muti = function () {
	 console.log("开始计算乘积");
	 var a = 1;
	 for (var i = 0, l = arguments.length; i < l; i++) {
		 a = a * arguments[i];
	 }
	 return a; 
};

缓存代理

var proxyMult = (function () {
	 var cache = {};
	 return function () {
		 var args = Array.prototype.join.call(arguments, ",");
		 if (args in cache) {
		 return cache[args];
	 }
	 return (cache[args] = mult.apply(this, arguments));
 }; })();
2.虚拟代理

虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建

常见的就是图片预加载

// 图片本地对象,负责往页面中创建一个img标签,并且提供一个对外的setSrc接口
let myImage = (function(){
	 let imgNode = document.createElement( 'img' );
	 document.body.appendChild( imgNode );
	 return {
		 //setSrc接口,外界调用这个接口,便可以给该img标签设置src属性
		 setSrc: function( src ){
			 imgNode.src = src;
		 }
	 } 
 })();
// 代理对象,负责图片预加载功能
let proxyImage = (function(){
	 // 创建一个Image对象,用于加载需要设置的图片
	 let img = new Image;
	 img.onload = function(){
		 // 监听到图片加载完成时后,给被代理的图片本地对象src为加载完成后的图片
		 myImage.setSrc( this.src );
	 }
	 return {
		 setSrc: function( src ){
		 // 设置图片时,在图片并未被真正加载好时,以这张图作为loading,提供用户图片正在加载
		 myImage.setSrc( 'https://img.zcool.cn/community/01deed57601906
		0000018c1bd2352d.gif' );
		 img.src = src;
	 }
 } })();
proxyImage.setSrc( 'https://xxx.jpg' );
3.保护代理

保护代理主要实现了访问主体的限制行为,以过滤字符作为简单的例子。

function sendMsg(msg) {
  console.log(msg);
}
function proxySendMsg(msg) {
  if (typeof msg === 'undefined') {
    console.log('deny')
    return
  }
  msg = ('' + msg).replace('z', '');
  sendMsg(msg)
}
sendMsg('zll')
proxySendMsg("zll")
proxySendMsg()