重新掌握JavaScript设计模式

发表时间: 2022-12-18 09:44

面试常常问到设计模式,设计模式在实际业务中即使有用到,往往在框架中会有更多体现,比如vue2源码,内部还是有很多设计思想,比如观察者模式,模版模式等,我们在业务上一些通用的工具类也会用到单例,在大量的条件判断也会考虑策略者模式,这两种用得比较多。好记性不如烂笔头,又重新回顾了一遍设计模式。

正文开始...

  • 单例模式

这种在业务里用得最多,常常是暴露一个全局的实例,或者是一个全局的类,所有的方法都是私有的,只能通过类访问内部方法

function Single () {  if (!Single.instance) {          Single.instance = this;  }  return Single.instance}const a = new Single()const b = new Single() // a === b; true
  • 策略者模式

我们通常在多个条件时,我们会考虑对象或者Map方式去替代我们的if条件,这是业务代码里常用的一种方式

const obj = {  a: function() {},  b: function() {}}const fn = (target) => {    return (key) => target[key]()}const geta = fn(obj)('a');const getb = fn(obj)('b');
  • 工厂模式

相似的对象,不知道应该用哪一种,根据输入的参数,自行决定创建指定类型的对象

var myApp = {};myApp.dom = {};myApp.dom.text = function(url) {    this.url = url;  this.insert = function(dom) {      const text = document.createTextNode(this.url);        dom.appendChild(text);    }}myApp.dom.img = function(url) {    this.url = url;  this.insert = function(dom) {      const img = document.createElement('image');    img.src = this.url;        dom.appendChild(img);    }}const textObj = new myApp.dom.text('http://www.learn.wmcweb.cn');textObj.insert(document.body);const imgObj = new myApp.dom.img('http://www.learn.wmcweb.cn')imgObj.insert(document.body);

我们可以通过一个中间函数,通过形参去确定哪个对象

myApp.dom.factor = function(type, url) {  return new myApp.dom[type](url)}const imgfator = myApp.dom.factor('img', 'http://www.learn.wmcweb.cn')const textFator = myApp.dom.factor('text', 'http://www.learn.wmcweb.cn')imgfator.insert(document.body);textFator.insert(document.body)
  • 装饰器模式

它是一种结构模型,与对象创建无关,如何拓展对象的功能

var person = {};person.say = function() {   this.nowTime = function() {      console.log('nowTime');   }}person.sleep = function() {  this.curentTime = function() {     console.log('this is current time');   }}person.getFn = function(type) {  person[type].prototype = this;  return new person[type]}person = person.getFn('say');console.log(person.nowTime()) // okconsole.log(person.curentTime()) // 报错

如果你像下面这样,就 ok了。当你person.getFn('sleep')执行时就扩展了对象的功能,能访问绑定在自身属性属性的curentTime方法了,但是你会发现,此时也可以访问nowTime方法

person = person.getFn('say');person = person.getFn('sleep');console.log(person.nowTime()) // okconsole.log(person.curentTime()) // ok
  • 观察者模式【发布订阅模式】

是一种行为模式,主要用于不同对象之间的交互信息

发布对象:重要事情发生时,会通知订阅者

订阅对象:监听发布对象的通知,并做出相应的反应

观察者主要分为两类:推送模式和拉动模式

推送模式是由发布者负责将消息发送给订阅者

拉动模式是订阅者主动跟踪发布者的状态变化

var observer = {};observer.list = []; // 存放订阅者的回调函数// 创建发布者observer.trigger = function() {  this.list.forEach(fn => {       fn.apply(this, arguments)    })}// 创建订阅者observer.addLinsten = function(fn) {   this.list.push(fn)}observer.addLinsten(function(week, msg) {        console.log(`今天${week}, ${msg}`)})observer.addLinsten(function(week, msg) {        console.log(`今天${week}, ${msg}`)})observer.trigger('周末', '不上班')
  • 命令模式
var btnClick = function(btn,callback) {  btn.onclick = callback;}const modal = {  open: function() {        console.log('open');  },  close: function() {        console.log('close');  }}btnClick(document.getElementById('app'), modal.open)btnClick(document.getElementById('app'), modal.close)
  • 模版模式
var Table = function() {}Table.prototype.drawHeader = function() {}Table.prototype.drawBody = function() {}Table.prototype.init = function() {   this.drawHeader();   this.drawBody();}const createTable = new Table();createTable.init();

如果还有一个类似的子类

var subTable = function() {}subTable.prototype = new Table();const stable = new subTable();stable.init(); 

我们再重新整合一下

const DrawTable = function(params){    const {drawHeader, drawBody} = params  const F = function() {};  F.prototype.init = function() {        drawHeader();        drawBody();      /*         // or         Object.keys(params).forEach(key => {           params[key]();         })      */    }  return F}const table = new DrawTable({    drawHeader() {    console.log('header');  },  drawBody() {        console.log('body')    }});table.init();
  • 适配器模式

通过一个统一的中间方法,做统一的适配调用

const render = (obj) => {   obj.start();}const airplane = {   start() {            console.log('飞机开始飞了')   }}const car = {   start() {       console.log('火车开始了');   }}render(airplane)render(car)

参考《javascript设计与开发实践》,下面afterbefore的设计很令人深思

Function.prototype.before = function(beforeFn) {   const self = this;   return function() {           beforeFn.apply(this, arguments);// 先执行回调函数       return self.apply(this, arguments) // 然后执行原函数    }}Function.prototype.after = function(afterFn) {    const self = this;  return function() {        const ret = self.apply(this, arguments);    afterFn.apply(this, arguments); // 执行当前的回调函数    return ret;  }}var func = function () {      console.log(2)}func = func.before(()=> {    console.log(1)}).after(() => {    console.log(3)});func(); // 1,2,3

《javascript设计与开发实践》中也有很多其他模式,比如代理模式,中介者模式,状态模式等,很多的设计模式实际上在业务代码里并不会用到,在某些特殊业务场景这些设计模式的思想会大大增强我们代码的拓展性,但过度的设计模式也会带来一定的阅读负担,凡事不可追求两全其美,只需要适可而止。

总结

  • 常用的设计模式,比如说单例模式,单例就是只对外暴露一个实例,所有的内部方法都是通过这个实例访问
  • 策略者模式是一种多条件的优化模式,当你在条件判断很多时,可以考虑策略者模式
  • 工厂模式,主要通过一个中间函数,通过形参输出对应的对象
  • 装饰器模式,主要是扩展对象的多个功能能力
  • 观察者模式也是发布订阅模式,主要有发布对象与订阅对象,订阅者监听发布对象的通知,做出响应,发布对象是有重要通知,统一通知所有订阅者
  • 另外看到一个利用闭包实现一个函数的before,after的例子
  • 本文示例主要参考《javascript设计与开发实践》《javascript面向对象编程指南(第二版)》

最后,看完觉得有收获的,点个赞,收藏等于学会,欢迎关注夜码者,好好学习,天天向上!