前端比较有用的设计模式

214 阅读7分钟

创建型模式

创建对象的模式, 抽象了实例化对象的过程

  • 单例模式

定义:保证一个类仅有一个实例,并提供这个实例的全局访问的方法。

应用场景: 创建全局公用的实例对象,例如,vuex和redux中的store。

class Single {
 constructor(name){
     this.name = name;
    }
    
    static getInstance(name){
     // 1. 创建全局的Symbol变量
        const instance = Symbol.for('Single');
        // 2. 如果instance不存在,创建新的Single实例,否则返回;
        if(!Single[instance]){
         Single[instance] = new Single(name)
        }
     return Single[instance]
    }
}

测试代码:

const single-1 = Single.getInstance('Single-1'); // {name: 'Single-1'}
const single-2 = Single.getInstance('Single-2');// {name: 'Single-1'}

console.log(single-1 === single-2); // true

除此之外创建型模式中还有 工厂模式 超级工厂模式, 感兴趣的同学可以自行研究。

结构型模式

主要解决怎样组装现有的对象,设计交互方式,而达到实现一定的功能目的。

  • 桥接模式 定义:将抽象部分和具体实现的部分分离,两者可独立变化,也可以一起工作。其实简单来书就是将函数传递给参数。

应用场景: 传递给组件的props,回调函数等等,例如数组map方法的回调函数。

// 自定义一个map函数
// map就相当于桥
const map = (arr,callback) => {
 if(!Array.isArray(arr)&&!callback) return;
    const returnArr = []
    for (const [index,item] of arr.entries()) {
     // 抽象部分
     let value = callback(item,index)
        if(value !== undefined){
         returnArr.push(value)
        }
    }
    return returnArr
}

测试代码:

const arr = [0,1,2,3,4,true,false];
map(arr,(item,index)=>{console.log(index,item); return item})
  • 代理模式

定义:为一个对象提供一种代理以方便对这个对象的访问。

代理模式可以解决避免对一些对象的直接访问。

代理模式细分为以下几种:

虚拟代理(将开销大的运算延迟到需要时执行) 常见

缓存代理(为开销大的运算结果提供缓存)

保护代理(黑白双簧,代理充当黑脸,拦截非分要求) 常见

防火墙代理(控制网络资源的访问)

远程代理(为一个对象在不同的地址控件提供局部代表)

智能引用代理(访问对象执行一些附加操作)

写时复制代理(延迟对象复制过程,对象需要真正修改时才进行)

代理模式的优缺点: 代理模式有高度解耦,对象保护,方便修改的特点。 但是因为是通过代理访问对象,因此开销会更大,时间更慢。

保护代理:

//例子:代理接电话,实现黑名单
// 准备数据;
let backPhoneList = ['8888888888888'];

// 代理
const ProxyAcceptPhone = (phoneNumber) => {
 // 预处理
    console.log('电话正在接入!')
    if(backPhoneList.includes(phoneNumber)){
      //屏蔽
      console.log("您的手机进入了黑名单");
    } else {
      //转接
      AcceptPhone.call(this, phoneNumber);
    }
}

// 本体
const AcceptPhone = (phoneNumber) => {
  console.log(`电话已经打通,电话号码为${phoneNumber}`);
};

//外部调用代理
ProxyAcceptPhone("999999999999");

缓存代理:

// 例子 计算分数

// 本体
let countScore = (ansList) => {
  let [a, b, c] = ansList;
  return a + b + c;
};

// 代理
const proxyCountScore = (() => {
  let existScore = {}; //存储对象
  return (ansList) => {
    let attr = ansList.join(",");
    if (existScore[attr] != null) {
      return existScore[attr];
    } else {
      return (existScore[attr] = countScore(ansList));
    }
  };
})();
// 调用代理
proxyCountScore([0, 1, 1]);

虚拟代理:

对虚拟代理运用最常见的就是图片预加载,常见的做法事先用一张loading图片占位,然后异步加载图片,待图片加载完成,把其填充到img节点里。

//虚拟代理的目的,是将开销大的运算延迟到需要时再执行。
// 本体
const setImage = (function(){
  const imageNode = document.createElement("img");
  document.body.appendChild(imageNode);
  return {
   setSrc(src){
     imageNode.src = src;
    }
  }
})();
// 代理
const proxyImage = (function(){
 const img = new Image();
    img.onload = function(){
     setImage.setSrc(this.src); // 图片加载完成设置真实的图片src
    };
    return {
     setSrc(src){
         setImage.setSrc('loading图片路径'); // 设置图片的src为loading图;
            img.src = src;
        }
    }
})()
// 外部调用
proxyImage.setSrc("真实的图片地址"// 有loading图片的预加载效果
  • 组合模式

定义:将对象组合成树形结构,以表示 部分-整体 的层次结构。

应用场景: 组合模式可以再需要针对'树形结构'进行操作的应用中使用,比如扫描文件夹,渲染网站导航结构等等。

// 组合模式

// 树的根节点,树对象
// 类目 类
class Category {
 constructor(name){
     this.name = name;
        this.subClass = [];
    }
    add(subClass){
     this.subClass.push(subClass);
    }
    scan(){
     // 遍历调用叶对象
        for(const subClass of this.subClass ){
         subClass.scan();
        }
    }
}

// 子类 类
class SubClass {
 constructor(name){
     this.name = name;
    }
 add() {
     throw new Error("子类不能再添加子类了");
    }
    scan() {
     console.log(`${this.name}`)
    }
}

测试代码:

// 创建类目对象
const movie = new Category("电影");
const music = new Category("音乐");
const media = new Category("娱乐");

// 创建子类目对象
const movieSubClass = new SubClass('复仇者联盟');
const musicSubClass = new SubClass('我为祖国献石油'); // 打败夏洛的双截棍

// 组合
movie.add(movieSubClass)
music.add(musicSubClass)
media.add(movie)
media.add(music)

行为型模式

定义:描述多个类或对象怎样交互以及怎样分配职责。

  • 发布-订阅模式

定义:定义了对象之间的一种一对多的依赖关系,当一个对象的状态改变时,所有依赖它的对象都可以得到通知。

class Event {
 constructor(){
     this.eventListObj = {}; // 缓存事件列表对象
    }
    
    // 单例模式
    static getInstance() {
      const instance = Symbol.for("instance");
      if (!Event[instance]) {
        Event[instance] = new Event();
      }
      return Event[instance];
   }
    
    // 添加监听事件,同一个指令,可以有多个事件
    listen(key, fn){
     if(!this.eventListObj[key]){
         this.eventListObj[key] = []
        }
        // 订阅消息添加进缓存列表
        this.eventListObj[key].push(fn);
    }
    
    // 触发监听事件
    trigger(key, ...args){
     const fns = this.eventListObj[key]
        if(fns && fns.length) {
         for(const item of fns){
             item.apply(this, args)
            }
        }
    }
    
    // 移除事件
    remove(key, fn){
     const fns = this.eventListObj[key];
        // 被订阅过才会操作
        if(fns){
         if(!fn){
             fns.length = 0;
            } else {
             for(const [index,item] of fns.entries()){
                 if(item === fn){
                     fns.splice(index, 1)
                    }
                }
            }
        }
    }
}

测试代码:

//注册全局的发布者
const event = Event.getInstance();

//创建回调函数
const add = (a, b) => {
  console.log(a + b);
};

const minus = (a, b) => {
  console.log(a - b);
};

//订阅加法消息
event.listen("add"add);
//订阅减法消息
event.listen("minus", minus);

//触发加法订阅消息
event.trigger("add"12);
//触发减法订阅消息
event.trigger("minus"31);
event.remove("add"add);
  • 观察者模式 定义: 当一个对象被修改时,则会自动通知依赖它的对象。概念几乎和发布-订阅的概念相同。
class Subject {
  constructor() {
    this.observers = []; // 观察者列表
  }

  add(observer) {
    this.observers.push(observer); // 添加观察者
  }

  remove(observer) {
    let index = this.observers.findIndex((item) => item === observer); // 找到要删除的观察者
    index > -1 && this.observers.splice(index, 1);
  }

  notify() {
    // 通知
    for (const observer of this.observers) {
      observer.update();
    }
  }
}

// 观察者
class Observer {
  constructor(name) {
    this.name = name;
  }

  // 目标对象跟新是触发的回调
  update() {
    console.log(`目标者通知我更新了:${this.name}`);
  }
}

测试代码:

const sub = new Subject()
const ob = new Observer('Observer')
sub.add(ob)
sub.notify(); // 目标者通知我更新了:Observer