第125题 前端常用的设计模式和使用场景

  • 工厂模式
    • 用一个工厂函数来创建实例,使用的时候隐藏new,可在工厂函数中使用newfunction factory(a,b,c) {return new Foo()}
    • jQuery$函数:$等于是在内部使用了new JQuery实例(用工厂函数$包裹了一下),可以直接使用$(div)
    • reactcreateElement
  • 单例模式
    • 全局唯一的实例(无法生成第二个)
    • Vuex Reduxstore
    • 如全局唯一的dialogmodal
    • 演示
                // 通过class实现单例构造器
        class Singleton {
          private static instance
          private contructor() {}
          public static getInstance() {
            if(!this.instance) {
              this.instance = new Singleton()
            }
            return this.instance
          },
          fn1() {}
          fn2() {}
        }
        
        // 通过闭包实现单例构造器
        const Singleton = (function () {
          // 隐藏Class的构造函数,避免多次实例化
          function FooService() {}
        
          // 未初始化的单例对象
          let fooService;
        
          return {
            // 创建/获取单例对象的函数
            // 通过暴露一个 getInstance() 方法来创建/获取唯一实例
            getInstance: function () {
              if (!fooService) {
                fooService = new FooService();
              }
              return fooService;
            }
          }
        })();
        // 使用
        const s1 = Singleton.getInstance()
        const s2 = Singleton.getInstance()
        // s1 === s2 // 都是同一个实例
  • 代理模式
    • 使用者不能直接访问对象,而是访问一个代理层
    • 在代理层可以监听get set做很多事
    • ES6 Proxy实现Vue3响应式
        var obj = new Proxy({},{
      get:function(target,key,receiver) {
        return Refect.get(target,key,receiver)
      },
      set:function(target,key,value,receiver) {
        return Refect.set(target,key,value,receiver)
      }
    })
  • 观察者模式
    • 观察者模式(基于发布订阅模式)有观察者,也有被观察者
    • 观察者需要放到被观察者中,被观察者的状态变化需要通知观察者 我变化了,内部也是基于发布订阅模式,收集观察者,状态变化后要主动通知观察者
        class Subject { // 被观察者 学生
      constructor(name) {
        this.state = 'happy'
        this.observers = []; // 存储所有的观察者
      }
      // 收集所有的观察者
      attach(o){ // Subject. prototype. attch
        this.observers.push(o)
      }
      // 更新被观察者 状态的方法
      setState(newState) {
        this.state = newState; // 更新状态
        // this 指被观察者 学生
        this.observers.forEach(o => o.update(this)) // 通知观察者 更新它们的状态
      }
    }
    
    class Observer{ // 观察者 父母和老师
      constructor(name) {
        this.name = name
      }
      update(student) {
        console.log('当前' + this.name + '被通知了', '当前学生的状态是' + student.state)
      }
    }
    
    let student = new Subject('学生'); 
    let parent = new Observer('父母'); 
    let teacher = new Observer('老师'); 
    
    // 被观察者存储观察者的前提,需要先接纳观察者
    student.attach(parent); 
    student.attach(teacher); 
    student.setState('被欺负了');
  • 发布订阅模式
    • 发布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态发生改变时,所依赖它的对象都将得到状态改变的通知。
    • 主要的作用(优点):
      • 广泛应用于异步编程中(替代了传递回调函数)
      • 对象之间松散耦合的编写代码
    • 缺点:
      • 创建订阅者本身要消耗一定的时间和内存
      • 多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护
    • 发布订阅者模式和观察者模式的区别?
      • 发布/订阅模式是观察者模式的一种变形,两者区别在于,发布/订阅模式在观察者模式的基础上,在目标和观察者之间增加一个调度中心。
      • 观察者模式 是由具体目标调度,比如当事件触发,Subject 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的(互相认识的)。
      • 发布/订阅模式 由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在(publishersubscriber是不认识的,中间有个Event Channel隔起来了)
      • 总结一下:
        • 观察者模式:SubjectObserver直接绑定,没有中间媒介。如addEventListener直接绑定事件
        • 发布订阅模式:publishersubscriber互相不认识,需要有中间媒介Event Channel。如EventBus自定义事件
    • 实现的思路:
      • 创建一个对象(缓存列表)
      • on方法用来把回调函数fn都加到缓存列表中
      • emit 根据key值去执行对应缓存列表中的函数
      • off方法可以根据key值取消订阅
                class EventEmiter {
          constructor() {
            // 事件对象,存放订阅的名字和事件
            this._events = {}
          }
          // 订阅事件的方法
          on(eventName,callback) {
            if(!this._events) {
              this._events = {}
            }
            // 合并之前订阅的cb
            this._events[eventName] = [...(this._events[eventName] || []),callback]
          }
          // 触发事件的方法
          emit(eventName, ...args) {
            if(!this._events[eventName]) {
              return
            }
            // 遍历执行所有订阅的事件
            this._events[eventName].forEach(fn=>fn(...args))
          }
          off(eventName,cb) {
            if(!this._events[eventName]) {
              return
            }
            // 删除订阅的事件
            this._events[eventName] = this._events[eventName].filter(fn=>fn != cb && fn.l != cb)
          }
          // 绑定一次 触发后将绑定的移除掉 再次触发掉
          once(eventName,callback) {
            const one = (...args)=>{
              // 等callback执行完毕在删除
              callback(args)
              this.off(eventName,one)
            }
            one.l = callback // 自定义属性
            this.on(eventName,one)
          }
        }
        
        // 测试用例
        let event = new EventEmiter()
        
        let login1 = function(...args) {
          console.log('login success1', args)
        }
        let login2 = function(...args) {
          console.log('login success2', args)
        }
        // event.on('login',login1)
        event.once('login',login2)
        event.off('login',login1) // 解除订阅
        event.emit('login', 1,2,3,4,5)
        event.emit('login', 6,7,8,9)
        event.emit('login', 10,11,12)  
  • 装饰器模式
    • 原功能不变,增加一些新功能(AOP面向切面编程)
    • ESTSDecorator语法就是装饰器模式

经典设计模式有23 个,这是基于后端写的,前端不是都常用

Last Updated:
Contributors: leeguooooo