js - 设计模式简单说

219 阅读5分钟

设计模式就是一种思想,代码整洁,易懂,维护,扩展,优化

单例模式

单例模式:基于单独的实例 管理某一个模块内容 从而实现模块的独立划分 也可以实现相互调用

  • 类只有一个实例
  • 全局可访问该实例(咱们这里利用闭包和函数作用域)

当项目开始的时候,划分模块,一人一个模块

    
var  A = (function(){
           var data = [];
           
           function change(val){
               data.push(val)
           }
           
           function handleClick(){ }
       }
       
       return {
           change
       }
    )()
    // 通过A 暴露出的方法,来实现相互调用
var  B = (function(){
           var data = [];
           
           function handleClick(){ }
       }
       
       A.change(11);
       
       return {
           handleClick
       }
    )()
    
  这种的模式的缺点也显而易见,大家都在同一个闭包下面,如果修改额话会影响到当前闭包内的东西,如果一旦想私有化,每个都有自己的容器就不好搞了
  
  A.change(10)
  A.change(20)
    
   

在业务开发中大家肯定遇过这种情况,需要先执行 1, 在执行 2, 在执行 3,是相互依赖关系,这时侯随之产生的

命令模式

咱们就拿单例 + 命令模式看下

  • 根据不同的命令来执行不同的操作

 let SearchModule = (
     function(){
         
         function QueryData () {};
         function bindHTML () {};
     }
     
     return {
          // 命令模式: 就相当于控制器,控制谁先执行,谁后执行 !
         init: function(){
             QueryData();
             bindHTML();
         }
     }
 )()
SearchModule.init()

咱们上面说的单例模式的缺点:这时候可以再看一把:

构造器模式

每个实例都属于自己,也就是有单独的一个空间,互相独立,有自己的私有属性和方法 or 公共方法 类 & 实例
私有 & 公有属性方法

    class A {
        constructor() {
            // this  = 每个类实例
            this.arr = [];
        }
        
        change(val){
            this.arr.push(val)
        }
        
       // 如果使用的是ES5如果放在函数内
       // 每new一次就需要重新创建,可以直接挂载到原型
       prompt(){
           console.log(this.arr)
       }
    }
    
    A.prototype.prompt = function (){
        console.log(this.arr)
    }
    
   var a1 = new A;
   var a2 = new A;
   console.log(a1 === a2) false
   console.log(a1.arr === a2.arr) false
   console.log(a1.change === a2.change) true // 因为都挂到原型上去了所以调用的是同一个方法

工厂模式

核心思想:中转站

可以帮助我们实现调用的切换 or 中转处理, 统一处理,流水线


function tory( options ){
    options = options || {};
    let { type, payload, } = options;
    if(type === 'array'){
        //处理一些逻辑
        return 
    }else {
       // 在处理另外的逻辑
        return
    }
}

观察者模式(vue2.0响应式原理)

目标一旦发生变化就会通知观察者做出相应的动作

上图:图 结合 代码 缕下思路

image.png

// 观察者
class Obsever{
    update(message) {
        // 消息送达,通知update进行
        console.log(message)
    }
}
// 观察者
class Demo{
    update(message) {
        // 消息送达,通知update进行
        console.log(message)
    }
}


//目标
class Subject {
      // 当然你也可以把这些方法抽离出去做一个观察者管理专属类,通过new 来调用
     constructor(){
        this.observerList = [];
    }
    add(observer){
        //可以做把去重
        this.observerList.push(observer)
        return this;
    }
    remove(){
        this.observerList =  this.observerList.filter(ob => ob !== observer)
        return this;
    }
    get(index){
        // 判断符合不符合要求
        return this.observerList[index]
    }
    count(){
        return this.observerList.length;
    }
    .........
    // 通知
    notify(...params) {
        // 为什么用for循环,因为for循环相比与forEach性能要好
        for(let i=0;i<this.count(); i++){
            let item = this.get(i);
            item.update(...params)
        }
    }
}

let sub = new Subject;
sub.add(new Obsever);
sub.add(new Demo);

setTimtout(() => {
    sub.notify('公司新品上市,欢迎大家抢购')
},1000)

中介者模式

托管第三方来搞,可以有效降低耦合度

image.png

// 由单例模式实现 中介者模式
let mediator = (function(){
    let topics = {};
    // 订阅:  A组件的方法             topic:可以理解为标识
    let subscribe = function subscribe(topic,callback){
        !topics[topic] ? topics[topic] = [] : null;
        topics[topic].push({
            context: this,
            callback
        })
    },
    // 发布:    B组件通知之前订阅的方法
    let publish = function publish(topic,...params){
        if(!topics[topic]) return;
        topics[topic].forEach( item => {
            let { callback,context } = item;
            callback.call(context,...params)
        })
    }
    return  {
        subscribe,
        publish
    }
})()


发布订阅模式

个人认为发布订阅模式很类似于 DOM2级事件 addEventListener

  • 给当前元素的某个事件绑定上方法 也算一个 事件池 机制

  • 事件行为触发,会依次通知事件池中的方法执行

  • 必须是内置事件(支持事件) 应用场景:某个阶段到达的时候,需要执行很多方法,都可以基于该模式

![image.png](p3-juejin.byteimg.com/tos-cn-i-k3… watermark.image)

用单例模式实现的话:


(function(){
    let pond = [];
    
    // 向事件池中注入自定义事件
    function subscribe(func){
        //可以做一些去重判断
        if(!pond.includes(func)) {
           pond.push(func)
        };
        
        // 同时做一个,移除当前新增的方法
        return function unSubscribe(){
            pond = pond.filter( itm => itm !== func)
        }
    }
    
    subscribe.fire = function(){
       pond.forEach(itm => {
           if(typeof itm === "function"){
               itm()
           }
       })
    }
    window.subscribe = subscribe;
})()

let unsubscribe = subscribe(function(){console.log(111)})

subscribe(function(){console.log(22222)})
subscribe(function(){console.log(333333),unsubscribe()})
subscribe(function(){console.log(444444)})
subscribe(function(){console.log(555555)})

setTimeout(() => {
 subscribe.fire()
},1000);


setTimeout(() => {
 subscribe.fire()
},3000)

上面介绍完ES5的版本,有个缺点式只有一个事件池,如果需要多个事件池的话就力不从心了,所以这时候咱们的需求式要支持多个事件池

那么 ES6面向对象来了

 class Sub {
    pond = [];
     // 原型上设置方法
    subscribe(func){
     let self = this;
     pond = self.pond;
        if(!pond.includes(func)) this.pond.push(func) 
       
        return function unsubscribe(){
          // 可以用filter
          let i = 0;
          let len = pond.length;
          for(;i<len;i++){
              if(pond[i] === func){
                  pond.splice(i,1)
                  break
              }
          }
        }
    }
    
    
    fire(...params){
        let self = this;
        self.pond.forEach(itm => {
            if(typeof itm === 'function){
                itm(...params)
            }
        })
    }
 }

当你看到这你可能很疑惑这个和 addEventListener 根本不太一样啊

好,接下来我基于上面单例写法扩展下,也可以基于ES6搞一下

let sub = ( function(){
    pond = {};
     // 原型上设置方法
    const on = (type,func) => {
      //首先判断pond中有没有该项,没有创建一个数组
      !Array.isArray(pond[type]) ? pond[type] = [] : null;
      let arr = pond[type];
      if(arr.includes(func)) return ;
      arr.push(func);
    }
    
    const remove = (type,func) => {
         let arr = pond[type];
         let i = 0;
         item = null;
       for(;i<arr.length; i++){
           if(item === func){
             //不能直接移除,否则会造成数组塌陷问题,赋值null就好,下次执行前filter为null的
             arr[i] = null;
             break;
           }
       }
    }
    
    const fire = (type,...params) => {
        let arr = pond[type];
        //也可以判断是不是数组
       i = 0;
       item = null;
       for(;i < arr.length; i++){
           item = arr[i];
            if(typeof itm === 'function'){
                itm(...params);
                continue;
            }
            // 将不是函数的都移除掉(包含null) 解决数组塌陷的i--
            arr.splice(i,1);
            i--
       }
    }
    return {
        on,
        remove,
        fire
    }
 })()
 
 
 const fn1 = () => console.log(1);
 const fn2 = () => console.log(2);
 const fn3 = () => console.log(3);
 const fn4 = () => console.log(4);

 sub.on('A',fn1)
 sub.on('A',fn2)
 sub.on('B',fn3)
 sub.on('B',fn4)
 
 setTimeout(() => {
  sub.fire('A',1)
 },1000)
 

后续继续更新