前端常用4个设计模式

设计模式

设计模式一般在软件开发的过程中有所应用,但在前端开发过程中也有时候会用到。个人理解设计模式是为了解决特定的问题而提出的一种解决方案,像是一个模板,用了可以使代码更加简洁和更加容易维护,可读性和结构性也会更强。
下面记录一下本次学习的4种比较常用的设计模式:单例模式、观察者模式、发布订阅模式、策略模式。

单例模式

如名称一般,单例模式下需要保证的有两点:

  1. 确保如何情况下都绝对只有一个实例
  2. 想在程序上表现出“只存在一个实例”

根据这两点可以得到如下代码:

function Person() {
  this.name = 'Jack'
}

let instance = null
function singleTon() {
  if (!instance) instance = new Person()return instance
}

const p1 = singleTon()
const p2 = singleTon()
console.log(p1 === p2)//true

可以看到现在不论调用多少次singleTon,都只会创建后自返回同一个实例。但这种做法有两个问题,明明是Person的实例,却需要通过singleTon来调用,和构造函数看起来没什么关系了;在创建实例的时候从表面看也丢失了new关键字。根据这两个问题对其进行改造。

const Person = (function () { //直接以构造函数名称命名
  function Person() { //把构造函数放到内部
    this.name = 'Jack'
    this.age = 18
  }
  Person.prototype.sayHi = function () { console.log('Hello, my name is', this.name) }

  // 利用闭包的形式来使instance一直存在
  let instance = null
  return function singleTon() {
    if (!instance) instance = new Person()
    return instance
  }
})() //立即执行函数

const p1 = new Person()
const p2 = new Person()
console.log(p1 === p2) //true

可以看到这次把两个问题都解决了。主要就是利用闭包来延长instance的生命周期,这里解释一下为什么可以使用new。new的本质其实是新建一个对象通过修改this来对新对象进行赋值,如果构造函数有返回一个对象了,那么就不会返回新建的对象。上述代码中会return instance,所以用不用new关键字都可以。

做个小练习,做一个自定义弹出层。
首先定义一个弹出层的类,并且点击拥有回调函数

 class Tip {
   constructor() {
     this.ele = document.createElement('div')
     this.ele.className = 'tip'

     //绑定事件
     this.bindEvent()

     //设置回调函数
     this.callback = function () {}

     document.body.appendChild(this.ele)
   }

   //设置提示内容
   //实际上应该一个节点一个节点创建,不能这么写,容易被注入恶意代码
   setContent(txt) {
     this.ele.innerHTML = `
     <div class="top">
       <div>标题</div>
       <button id='cancle'>X</button>
     </div>
     <div class="content">
       <p>${txt}</p>
     </div>
     <div class="btns">
       <button id='cancle'>取消</button>
       <button id='ok'>确定</button>
     </div>
   `
     this.ele.style.display = 'block'
   }

   bindEvent() {
     this.ele.addEventListener('click', e => {
       e = e || window.event
       const target = e.target || e.srcElement

       if (target.id === 'cancle') {
         this.ele.style.display = 'none'
       } else if (target.id === 'ok') {
         this.callback() //ok的时候执行回调函数
         this.ele.style.display = 'none'
       }
     })
   }
 }

const tip = new Tip()
tip.setContent('hahaha', () => { console.log('回调函数1')})
tip.setContent('hello world', () => { console.log('回调函数2')})

接下来就要用到单例模式了,按照上面那样把类包在一个函数里面:

const Tip = (function () {
  class Tip {...}
  let instance = null
  return function singleTon(txt, cb) {
    if (!instance) instance = new Tip()

    instance.setContent(txt)  //设置提示内容
    instance.callback = cb  //设置回调函数
    return instance
  }
})()

const tip1 = new Tip("你好", () => {
  console.log("回调函数1")
})
const tip2 = new Tip("世界", () => {
  console.log("回调函数2")
})
console.log(tip1 === tip2)  //true

不论我们new多少次,只有一个实例对象,这很好地节约了内存,也省去了每次创建和删除节点了工作。
在这里我们第一个参数只传了字符串,实际上可以传一个配置项,可以修改样式等等,这样子自由度就高了很多。
单例模式适用于模板不变,内容变的情景,例如本例。

观察者模式

观察者模式有两个对象,一个是观察者,一个是被观察者,当被观察者的状态发生改变的时候,需要通知观察者,观察者会做出相对的反应。

//观察者
class Observer {
  constructor(name, fn = () => { }) {
    this.name = name
    this.fn = fn
  }
}

//被观察者
class Subject {
  constructor(name, state) {
    this.name = name
    this.state = state
    this.observers = []
  }

  //设置状态
  setState(val) {
    this.state = val
    //通知观察者
    this.notify()
  }

  //添加观察者
  addObserver(obs) {
    //不存在才能添加
    if (this.observers.indexOf(obs) == -1) {
      this.observers.push(obs)
    }
  }

  //删除观察者
  deleteObserver(obs) {
    //使用过滤,得到不包含obs的数组
    this.observers = this.observers.filter(item => item !== obs)
  }

  //通知观察者
  notify() {
    this.observers.forEach(item => {
      item.fn(this.name, this.state)
    })
  }
}

const obs1 = new Observer("观察者1", (sub, state) => { console.log('观察者1发现' + sub + '的状态修改为' + state) })
const obs2 = new Observer("观察者2", (sub, state) => { console.log('观察者2发现' + sub + '的状态修改为' + state) })
const sub = new Subject("被观察者", "学习")
sub.addObserver(obs1)
sub.addObserver(obs2)
sub.setState("玩游戏")
sub.addObserver(obs2)
console.log(sub.observers)
sub.deleteObserver(obs1)
console.log(sub.observers)
sub.setState("打球")

观察者模式适用于根据对象状态进行相应处理的场景。

发布订阅模式

对观察者模式和发布订阅模式,有人认为是一样的,也有人认为不一样。不一样的原因的发布订阅模式在结构上和观察者模式就有区别。观察者模式是在被观察者状态发生改变的时候一一通知观察者,他们都知道对方的存在。而发布订阅模式,在发布者和订阅者中间有一个中介,负责对事件进行调度,发布者和订阅者不知道对方是否存在。

class PubSub {
  constructor() {
    //订阅列表
    this.subscribers = {}
  }

  //订阅类型和处理函数
  subscribe(type, fn = () => { }) {
    if (!this.subscribers[type]) {
      this.subscribers[type] = []
    }
    this.subscribers[type].push(fn)
  }

  //取消订阅
  unsubscribe(type, fn) {
    // 1、取消整个类型的订阅
    if (!fn) {
      delete this.subscribers[type]
    }

    // 2、取消具体的订阅
    if (!this.subscribers[type]) return
    this.subscribers[type] = this.subscribers[type].filter(item => item !== fn)
  }

  //发布
  publish(type, ...args) {
    if (!this.subscribers[type]) return
    this.subscribers[type].forEach(fn => {
      fn(...args)
    })
  }
}

const pb = new PubSub()
pb.subscribe("小说", (val) => console.log(val))
pb.publish("小说", "您订阅的小说更新了")
pb.subscribe("动漫", (val) => console.log(val))
pb.publish("动漫", "您订阅的动漫托更了")

注意这里使用箭头函数(匿名函数)当作回调函数是不能取消订阅的了,因为找不到他对应的地址了,所以最好把函数单独写出来。
可以看到和观察者模式还是有点区别的,发布者和订阅者的耦合度更低了,代码也更加的简洁。

策略模式

策略(Strategy)模式可以整体地替换算法的实现部分,能让我们轻松地以不同的算法去解决同一个问题。这里提出一个应用场景,就是我们常见的购物车,在结算的时候我们可能有多种折扣方案可以选择,我们可以任意选择其中一种方案,有时我们还能看到该优惠券不可用等- -。
那么现在就有一个问题,优惠的方案是会不断变化的,对应的算法也会变化,我们总不能每次都去修改源代码的算法部分吧,我们需要更加便捷的方法,能够快速添加和删除一种方案,并且选择某种方案后可以计算出优惠后的价格。

const calPrice = (function () {
  //折扣类型以及计算方法
  const sale = {
    '100-10': function (price) { return price -= 10 },
    '200-25': function (price) { return price -= 25 },
    '90%': function (price) { return price *= 0.9 }
  }

  //返回使用折扣后的价格
  function calPrice(type, price) {
    if (!sale[type]) return '该折扣不可用'

    return sale[type](price)
  }

  //添加一种折扣
  calPrice.add = function (type, fn) {
    if (sale[type]) return '该折扣已存在'

    sale[type] = fn
  }

  //删除一种折扣
  calPrice.del = function (type) {
    delete sale[type]
  }

  //获取所有折扣
  calPrice.getDis = function () {
    return Object.keys(sale)
  }

  //闭包
  return calPrice
})()

calPrice.add('618', (price) => { return price *= 0.8 })
console.log(calPrice('618', 500))
console.log(calPrice.getDis())
calPrice.del('100-10')
console.log(calPrice.getDis())

在这种结构下,我们可以主动添加或者删除折扣,也可以列出所有的折扣种类,然后根据用户选择的折扣来返回计算的结果。设想一下,如果是写成一堆if和else,那么每次修改折扣的时候就要修改源代码,不容易维护和更加任意出错。
所以设计模式是非常重要的,选择好了以后轻松很多。这次先学习这4种设计模式,后面有再学习的时候会继续补充,如果有问题欢迎提出~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值