前端常用设计模式小记

226 阅读6分钟

《JavaScript设计模式与开发实践》小笔记

单例模式

定义

保证一个类仅有一个实例,并提供全局访问。

应用场景

全局缓存、浏览器window对象,登录弹窗

代码实现

惰性单例模式,只有调用时才会创建

const createSingleton = ()=>{
  let singleton = null
  return ()=>{
    if(!singleton){
      singleton = new Singletion()
    }
    return singleton
  }
}

策略模式

定义

封装一系列的算法,并且使他们可以相互替换

应用场景

当代码中含有大量if-else语句时

代码实现

在JS中,策略函数已经融入语言本身,我们经常用高阶函数来封装不同的行为

// 用类型实现策略模式
const STRATEGIES = {
  'S':(salary)=>{
    return salary*4
  },
  'A':(salary)=>{
    return salary*2
  },
}
const calculate =(level,salary)=>{
  return STRATEGIES[level](salary)
}
// 用高阶函数实现策略模式
const S = (salary)=>(salary*4)
const A = (salary)=>(salary*2)
const calculateSalary = (func,salary)=>{
	return func(salary)
}

代理模式

定义

为一个对象提供代理,以便控制对他的访问

应用场景

图片懒加载:代理对象可以在加载图片时显示占位符,当图片加载完成后再替换占位符,从而提高页面加载速度和用户体验。

缓存:为一些开销大的运算结果提供暂时的存储,在下次运算之前,如果传递进来的参数跟之前一直,这直接返回前面的存储结果

代码实现

const calculate = (x) => {
  // huge... x+1

};
const proxy = ((calculate) => {
  let cache = {};
  return () => {
    // 以argument作为key值
    let args = arguments.join(",");
    if (args in cache) {
      return cache[args];
    }
    return (cache[args] = calculate(arguments));
  };
})(calculate);
proxy(1, 2, 3, 4);

迭代器模式

定义

提供一种方案顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

应用场景

同上

发布-订阅模式

定义

定义对象件一种一对多依赖关系,当一个对象的状态发生改变时,所有依赖与它的对象都将得到通知

应用场景

应用于异步编程中,当在ajax的error和succ事件中完成后需要做一些事,那么就可以订阅一个事件。另外,发布-订阅事件也可以让两个对象松耦合地联系在一起。但是,创建订阅者会消耗一定的内存,过度使用的话也会导致程序难以维护

命令模式

定义

请求以命令的形式包裹在对象中,并传给调用对象

应用场景

向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道请求的具体操作是什么。这时使用命令模式,消除彼此之间的耦合关系(在JS中可以使用高阶函数实现)

模板方法模式

定义

在父类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。

应用场景

假设我们有一些平行的子类,各子类之间有一些相同的行为,也有一些不同的行为,可以将相同的行为移动到父类中,不同的部分留给子类。(使用抽象类即可实现)

享元模式

定义

用于减少创建对象的数量,以减少内存占用已经提高性能。

应用场景

  1. 大量相似对象
  2. 对象的大多数状态都是外部状态
  3. 剥离出对象的外部状态后,可以用相对较少对的共享对象取代大量对象

内部状态:通常不会改变。外部状态:取决于场景变化而变化,不能共享

职责链模式

定义

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递请求,知道有一个对象处理他为止。

应用场景

某个场景如果具有多个if分支判断,则可以将每个分支包装成函数一次调用。另外,能够灵活拆分重组代码逻辑

举个例子:超市促销,vip用户优惠300元,普通用户优惠100元。可以写成如下方式

function discount(role, count) {
  if (role === "vip") {
    count -= 300;
  }
  if (role === "normal") {
    count -= 100;
  }
  return count;
}
// 拆成职责链
const vipdiscount = () => {};
const normalDiscount = () => {};
const chain = (fn) => {
  this.fn = fn;
  this.successor = null;
};
chain.prototype.setNextSucessor = (successor) => {
  return (this.succrssor = successor);
};
chainOrdervip = new chain(vipdiscount);
chainOrdervip.setNextSucessor(normalDiscount);

中介者模式

定义

能够解除对象之间的紧耦合关系。

应用场景

(类似于Vue中的watch,React中的useEffect)。多个对象之间的耦合关系非常复杂并且相互影响时,可以用中介者模式解耦。举个例子,A商品有颜色,重量,保质期多个选择条件时(颜色修改时独自计算商品数量的逻辑,重量修改时独自计算商品数量的逻辑…)可以新建一个中介者,将修改的属性传入中介者模式中,让他计算商品的剩余数量。这样就不需要商品每个属性颜色修改时独自维护一组计算商品数量的逻辑。

装饰者模式

定义

给对象动态增加职责的方式

应用场景

埋点数据上报:分离业务代码和数据上报代码。

表单提交功能:分离表单验证代码和发送请求代码。

装饰器与代理模式 代理模式强调增加一层关系以为对象提供或拒绝对他的访问。装饰器模式强调直接为对象动态加入行为

代码实现

// 这里只实现before和after
Function.prototype.before =function (beforefn){
	let self = this
	return function () {
		beforefn.apply(this,arguments)
		return self.apply(this,arguments)		
	}
}

Function.prototype.after=function (afterfn){
	let self = this
	return function (){
		let ret = self.apply(this,arguments)
		afterfn.apply(this,arguments)
		return ret		
	}
}
let a = function(){console.log('a')}
let b = function(){console.log('b')}
a = a.before(b)
a()

状态模式

定义

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类

应用场景

一个对象中状态与行为逻辑相对复杂时,可以将状态和逻辑封装到一个类里面,避免了上下文相对膨胀,也避免了过多的ifelse。举个例子,下载文件中有等待,下载,暂停3个状态

const STATUS = {
  download: {
    status: "download",
    process: () => {
      console.log("我正在下载");
    },
  },
  wait: {
    status: "wait",
    process: () => {
      console.log("我正在等待");
    },
  },
  pause: {
    status: "pause",
    process: () => {
      console.log("我正在暂停");
    },
  },
};

与策略模式的区别 同样是对算法的封装,但是策略模式之间算法切换是平行的。在状态机之间状态切换是已经内部规定好的。

适配器模式

定义

在两个不适配的接口中增加一个对象,将一个接口转化为另一个适配的接口

应用场景

解决两个已有的接口之间不匹配的问题