【青训营】- JS中的设计模式

169 阅读4分钟

JS中的设计模式

是什么

在软件设计过程中,针对特定问题的简介而优雅的解决方案

经验总结=>合理运用=>解决问题

SOLID设计原则

S单一职责原则

一个模块只做一件事

O开放封闭原则

对扩展开放,对修改封闭

L里氏置换原则

子类能覆盖父类,并能出现在父类出现的地方

I接口独立原则

保持接口的单一独立

D依赖导致原则

使用方法只关注接口而不关注具体类的实现

为什么

易读性

使用设计模式能提升代码的可读性,提升后续开发效率

可扩展性

使用设计模式对代码解耦,能很好的增强代码的易修改性和扩展性

复用性

使用设计模式可以重用已有的解决方案,无需重复相同工作

可靠性

使用设计模式能增加系统的健壮性,使代码编写真正工程化

常见设计模式

单例模式

定义

唯一&全局访问。保证一个类仅有一个实例,并提供一个访问它的全局访问点

应用场景

能被缓存的内容?,如登录弹窗

Example

Before

const createLoginLayer = () => {
  const div = document.createElement("div");
  div.innerHTML = "登录浮窗";
  div.style.display = "none";
  document.body.appendChild(div);
  return div;
};

document.getElementById("loginBtn").onclick = () => {
  const loginLayer = createLoginLayer();
  loginLayer.style.display = "block";
};

After

const getSingle = (fn) => {
  let result;
  return (...rest) => {
    return result || (result = fn.apply(this, rest));
  };
};

const createLoginLayer = () => {
  const div = document.createElement("div");
  div.innerHTML = "登录浮窗";
  div.style.display = "none";
  document.body.appendChild(div);
  return div;
};

const createSingleLoginLayer = getSingle(createLoginLayer);

document.getElementById("loginBtn").onclick = () => {
  const loginLayer = createSingleLoginLayer();
  loginLayer.style.display = "block";
};

优点

节省内存,提高性能。(相当于缓存)

策略模式

定义

定义一系列的算法,把它们一个个封装起来,并使他们可以互相替换。把看似毫无联系的代码提取封装、复用,使之更容易拓展和理解。

应用场景

要完成一件事,有不同的策略。例如绩效计算、表单验证规则。

Example

Before

const calculateBonus = (level,salary) => {
  switch(level){
    case 's':{
      return salary*4
    }
    case 'a':{
      return salary*3
    }
    case 'b':{
      return salary*2
    }
    default:{
      return 0
    }
  }
  
}

calculateBonus('s',20000)
calculateBonus('a',10000)

After

const strategies = {
  s: (salary) => salary * 4,
  a: (salary) => salary * 3,
  b: (salary) => salary * 2,
};
const calculateBonus = (level, salary) => strategies[level](salary);

calculateBonus("s", 20000);
calculateBonus("a", 10000);

优点

把策略和业务逻辑解耦,只要去维护策略就行了

代理模式

定义

为一个对象提供一个代用品或占位符,以便控制对它的访问。替身对象可对请求预先进行处理,再决定是否转交给本体对象。

应用场景

当我们不方便直接访问某个对象时,或不满足需求时,可考虑使用一个替身对象来控制对该对象的访问。

Example

Before

const rawImage = (() => {
  const imgNode = document.createElement("img");
  document.body.appendChild(imgNode);
  return {
    setSrc: (src) => {
      imgNode.src = "./loading.gif";
      const img = new Image();
      img.src = src;
      img.onload = () => (imgNode.src = this.src);
    },
  };
})();

rawImage.setSrc("http://xxx.gif");

After

const rawImage = (() => {
  const imgNode = document.createElement("img");
  document.body.appendChild(imgNode);
  return {
    setSrc: (src) => {
      imgNode.src = src;
    },
  };
})();

//代理函数
const proxyImage = (() => {
  const img = new Image();
  img.onload = () => {
    rawImage.setSrc(this.src);
  };
  return {
    setSrc: (src) => {
      rawImage.setSrc("./loading.gif");
      img.src = src;
    },
  };
})();

proxyImage.setSrc("http://xxx.gif");

优点

可以将预处理和实际处理解耦

发布订阅模式!!!

定义

对象间一种一对多的依赖关系,当一个对象状态发生改变时,所以依赖于它的对象都将得到通知。

应用场景

DOM事件,消息通知。

Example

class PubSub {
  constructor() {
    this.subscribers = {};
  }

  subscribe(type, fn) {
    const listeners = this.subscribers[type] || [];
    listeners.push(fn);
  }

  unsubscribe(type, fn) {
    const listeners = this.subscribers[type];
    if (!listeners || !listeners.length) return;
    this.subscribers[type] = listeners.filter((listener) => listener !== fn);
  }

  publish(type, ...args) {
    const listeners = this.subscribers[type];
    if (!listeners || !listeners.length) return;
    listeners.forEach((fn) => fn(...args));
  }
}

const ob = new PubSub();
ob.subscribe("add", (val) => console.log(val));
ob.publish("add", 1);

命令模式

定义

执行某些特定事情的指令

应用场景

富文本编辑器工具栏

Example

const setCommand = (button, command) => {
  button.onclick = () => {
    command.execute();
  };
};

//业务逻辑:富文本编辑器菜单栏里有个刷新按钮,点击之后刷新文本内容
const MenuBar = {
  refresh: () => {
    console.log("refresh");
  },
};

const RefreshMenuBarCommand = (receiver) => {
  return {
    execute: () => {
      receiver.refresh();
    },
  };
};

const refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);

setCommand(refreshButton, refreshMenuBarCommand);

组合模式

定义

用小的子对象来构建更大的对象,将对象组合成树形结构,以表示“部分-整体的层次结构”

应用场景

从is-a到has-a

Example

class MacroCommand{
  constructor(){
    this.commands = []
  }

  //添加子对象逻辑
  add(command){
    console.log(this)
    this.commands.push(command)
  }

  //执行父对象逻辑
  execute(){
    for(let i=0;i<this.commands.length;i++){
      this.commands[i].execute()
    }
  }
}

const macroCommand = MacroCommand()
const openCommand = {
  execute:()=>{
    console.log('open')
  }
}

const closeCommand =  {
  execute:()=>{
    console.log('close')
  }
}

macroCommand.add(openCommand)
macroCommand.add(closeCommand)
macroCommand.execute()

装饰器模式

定义

能够在不改变对象自身的基础上,在程序运行期间动态地添加职责

应用场景

数据上报,统计函数执行时间,中间件

Example

Before

const ajax = (type, url, param) => {
  console.log(param);
};

const getToken = () => "Token";

ajax();
getToken();

After

//添加职责
Function.prototype.before = function (beforeFn) {
  return (...rest) => {
    beforeFn.apply(this, rest);
    return this(...rest);
  };
};

let ajax = (type, url, param) => {
  console.log(param);
};

const getToken = () => {
  return "Token";
};

ajax = ajax.before((type, url, param) => {
  param.token = getToken();
});

适配器模式

定义

解决两个软件实体间的接口不兼容问题,不需要改变已有接口,就能使他们协同作用(有点想转换器)

应用场景

接口不兼容的情况

Example

Before

const aMap = {
  show: () => {
    console.log("开始渲染地图A");
  },
};

const bMap = {
  display: () => {
    console.log("开始渲染地图B");
  },
};

const renderMap = (type) => {
  if (type === "a") {
    aMap.show();
  } else if (type === "b") {
    bMap.display();
  }
};

After

const aMap = {
  show: () => {
    console.log("开始渲染地图A");
  },
};

const bMap = {
  display: () => {
    console.log("开始渲染地图B");
  },
};

//适配层
const bMapAdapter = {
  show: () => {
    return bMap.display();
  },
};

const renderMap = (map) => {
  map.show instanceof Function && map.show();
};

总结

理解思想

合理使用

总结经验

融会贯通