JS常用设计模式

更新日期: 2019-07-19阅读: 2k标签: 模式
设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案

设计模式是前人解决某个特定场景下对而总结出来的一些解决方案。可能刚开始接触编程还没有什么经验的时候,会感觉设计模式没那么好理解,这个也很正常。有些简单的设计模式我们有时候用到,不过没意识到也是存在的。

学习设计模式,可以让我们在处理问题的时候提供更多更快的解决思路。

当然设计模式的应用也不是一时半会就会上手,很多情况下我们编写的业务逻辑都没用到设计模式或者本来就不需要特定的设计模式。


适配器模式

这个使我们常使用的设计模式,也算最简单的设计模式之一,好处在于可以保持原有接口的数据结构不变动。

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。

例子

适配器模式很好理解,假设我们和后端定义了一个接口数据结构为(可以理解为旧接口):

[
  {
    "label": "选择一",
    "value": 0
  },
  {
    "label": "选择二",
    "value": 1
  }
]

但是后端后面因为其他原因,需要定义返回的结构为(可以理解为新接口):

[
  {
    "label": "选择一",
    "text": 0
  },
  {
    "label": "选择二",
    "text": 1
  }
]

然后我们前端的使用到后端接口有好几处,那么我可以把新的接口字段结构适配为老接口的,就不需要各处去修改字段,只要把源头的数据适配好就可以了。

当然上面的是非常简单的场景,也是经常用到的场景。或许你会认为后端处理不更好了,的确是这样更好,但是这个不是我们讨论的范围。


单例模式

单例模式,从字面意思也很好理解,就是实例化多次都只会有一个实例。

有些场景实例化一次,可以达到缓存效果,可以减少内存占用。还有些场景就是必须只能实例化一次,否则实例化多次会覆盖之前的实例,导致出现 bug(这种场景比较少见)。

例子

实现弹框的一种做法是先创建好弹框, 然后使之隐藏, 这样子的话会浪费部分不必要的 dom 开销, 我们可以在需要弹框的时候再进行创建, 同时结合单例模式实现只有一个实例, 从而节省部分 DOM 开销。下列为登入框部分代码:

const createLoginLayer = function() {
  const div = document.createElement('div')
  div.innerhtml = '登入浮框'
  div.style.display = 'none'
  document.body.appendChild(div)
  return div
}

使单例模式和创建弹框代码解耦

const getSingle = function(fn) {
  const result
  return function() {
    return result || result = fn.apply(this, arguments)
  }
}
const createSingleLoginLayer = getSingle(createLoginLayer)

document.getElementById('loginBtn').onclick = function() {
  createSingleLoginLayer()
}


代理模式

代理模式的定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。

代理对象拥有本体对象的一切功能的同时,可以拥有而外的功能。而且代理对象和本体对象具有一致的接口,对使用者友好。

虚拟代理

下面这段代码运用代理模式来实现图片预加载,可以看到通过代理模式巧妙地将创建图片与预加载逻辑分离,,并且在未来如果不需要预加载,只要改成请求本体代替请求代理对象就行。

const myImage = (function() {
  const imgNode = document.createElement('img')
  document.body.appendChild(imgNode)
  return {
    setSrc: function(src) {
      imgNode.src = src
    }
  }
})()

const proxyImage = (function() {
  const img = new Image()
  img.onload = function() { // http 图片加载完毕后才会执行
    myImage.setSrc(this.src)
  }
  return {
    setSrc: function(src) {
      myImage.setSrc('loading.jpg') // 本地 loading 图片
      img.src = src
    }
  }
})()

proxyImage.setSrc('http://loaded.jpg')

缓存代理

在原有的功能上加上结果缓存功能,就属于缓存代理。

原先有个功能是实现字符串反转(reverseString),那么在不改变reverseString 的现有逻辑,我们可以使用缓存代理模式实现性能的优化,当然也可以在值改变的时候去处理下其他逻辑,如 vue computed 的用法。

function reverseString(str) {
  return str
    .split('')
    .reverse()
    .join('')
}
const reverseStringProxy = (function() {
  const cached = {}
  return function(str) {
    if (cached[str]) {
      return cached[str]
    }
    cached[str] = reverseString(str)
    return cached[str]
  }
})()


订阅发布模式

订阅发布使前端常用的数据通信方式、异步逻辑处理等等,如 react setState 和 Redux 就是订阅发布模式的。但是要合理的使用订阅发布模式,否则会造成数据混乱,redux 的单向数据流思想可以避免数据流混乱的问题。

例子

class Event {
  constructor() {
    // 所有 eventType 监听器回调函数(数组)
    this.listeners = {}
  }
  /**
   * 订阅事件
   * @param {String} eventType 事件类型
   * @param {Function} listener 订阅后发布动作触发的回调函数,参数为发布的数据
   */
  on(eventType, listener) {
    if (!this.listeners[eventType]) {
      this.listeners[eventType] = []
    }
    this.listeners[eventType].push(listener)
  }
  /**
   * 发布事件
   * @param {String} eventType 事件类型
   * @param {Any} data 发布的内容
   */
  emit(eventType, data) {
    const callbacks = this.listeners[eventType]
    if (callbacks) {
      callbacks.forEach((c) => {
        c(data)
      })
    }
  }
}

const event = new Event()
event.on('open', (data) => {
  console.log(data)
})
event.emit('open', { open: true })


观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

Vue 的数据驱动就是使用观察者模式,mbox 也是使用观察者模式。

例子

模仿 Vue 数据驱动渲染模式(只是类似,简单的模仿)。

首先使用 setter 和 getter 监听到数据的变化:

const obj = {
  data: { description: '' },
}

Object.defineProperty(obj, 'description', {
  get() {
    return this.data.description
  },
  set(val) {
    this.data.description = val
  },
})

然后加上目标和观察者

class Subject {
  constructor() {
    this.observers = []
  }

  add(observer) {
    this.observers.push(observer)
  }

  notify(data) {
    this.observers.forEach((observer) => observer.update(data))
  }
}

class Observer {
  constructor(callback) {
    this.callback = callback
  }
  update(data) {
    this.callback && this.callback(data)
  }
}

// 创建观察者ob1
let ob1 = new Observer((text) => {
  document.querySelector('#dom-one').innerHTML(text)
})
// 创建观察者ob2
let ob2 = new Observer((text) => {
  document.querySelector('#dom-two').innerHTML(text)
})
// 创建目标sub
let sub = new Subject()
// 目标sub添加观察者ob1 (目标和观察者建立了依赖关系)
sub.add(ob1)
// 目标sub添加观察者ob2
sub.add(ob2)
// 目标sub触发事件(目标主动通知观察者)
sub.notify('这里改变了')

组合在一起是这样的

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover"
    />
    <title></title>
  </head>
  <body>
    <div id="app">
      <div id="dom-one">
        原来的值
      </div>
      <br />
      <div id="dom-two">
        原来的值
      </div>
      <br />
      <button id="btn">改变</button>
    </div>
    <script>
      class Subject {
        constructor() {
          this.observers = []
        }

        add(observer) {
          this.observers.push(observer)
        }

        notify() {
          this.observers.forEach((observer) => observer.update())
        }
      }

      class Observer {
        constructor(callback) {
          this.callback = callback
        }
        update() {
          this.callback && this.callback()
        }
      }

      const obj = {
        data: { description: '' },
      }

      // 创建观察者ob1
      const ob1 = new Observer(() => {
        console.log(document.querySelector('#dom-one'))
        document.querySelector('#dom-one').innerHTML = obj.description
      })
      // 创建观察者ob2
      const ob2 = new Observer(() => {
        document.querySelector('#dom-two').innerHTML = obj.description
      })
      // 创建目标sub
      const sub = new Subject()
      // 目标sub添加观察者ob1 (目标和观察者建立了依赖关系)
      sub.add(ob1)
      // 目标sub添加观察者ob2
      sub.add(ob2)

      Object.defineProperty(obj, 'description', {
        get() {
          return this.data.description
        },
        set(val) {
          this.data.description = val
          // 目标sub触发事件(目标主动通知观察者)
          sub.notify()
        },
      })
      btn.onclick = () => {
        obj.description = '改变了'
      }
    </script>
  </body>
</html>


装饰者模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。

ES6/7 的decorator 语法提案,就是装饰者模式。

例子

class A {
  getContent() {
    return '第一行内容'
  }
  render() {
    document.body.innerHTML = this.getContent()
  }
}

function decoratorOne(cla) {
  const prevGetContent = cla.prototype.getContent
  cla.prototype.getContent = function() {
    return `
      第一行之前的内容
      <br/>
      ${prevGetContent()}
    `
  }
  return cla
}

function decoratorTwo(cla) {
  const prevGetContent = cla.prototype.getContent
  cla.prototype.getContent = function() {
    return `
      ${prevGetContent()}
      <br/>
      第二行内容
    `
  }
  return cla
}

const B = decoratorOne(A)
const C = decoratorTwo(B)
new C().render()


策略模式

在策略模式(Strategy Pattern)中,一个行为或其算法可以在运行时更改。

假设我们的绩效分为 A、B、C、D 这四个等级,四个等级的奖励是不一样的,一般我们的代码是这样实现:

/**
 * 获取年终奖
 * @param {String} performanceType 绩效类型,
 * @return {Object} 年终奖,包括奖金和奖品
 */
function getYearEndBonus(performanceType) {
  const yearEndBonus = {
    // 奖金
    bonus: '',
    // 奖品
    prize: '',
  }
  switch (performanceType) {
    case 'A': {
      yearEndBonus = {
        bonus: 50000,
        prize: 'mac pro',
      }
      break
    }
    case 'B': {
      yearEndBonus = {
        bonus: 40000,
        prize: 'mac air',
      }
      break
    }
    case 'C': {
      yearEndBonus = {
        bonus: 20000,
        prize: 'iphone xr',
      }
      break
    }
    case 'D': {
      yearEndBonus = {
        bonus: 5000,
        prize: 'ipad mini',
      }
      break
    }
  }
  return yearEndBonus
}

使用策略模式可以这样:

/**
 * 获取年终奖
 * @param {String} strategyFn 绩效策略函数
 * @return {Object} 年终奖,包括奖金和奖品
 */
function getYearEndBonus(strategyFn) {
  if (!strategyFn) {
    return {}
  }
  return strategyFn()
}

const bonusStrategy = {
  A() {
    return {
      bonus: 50000,
      prize: 'mac pro',
    }
  },
  B() {
    return {
      bonus: 40000,
      prize: 'mac air',
    }
  },
  C() {
    return {
      bonus: 20000,
      prize: 'iphone xr',
    }
  },
  D() {
    return {
      bonus: 10000,
      prize: 'ipad mini',
    }
  },
}

const performanceLevel = 'A'
getYearEndBonus(bonusStrategy[performanceLevel])

这里每个函数就是一个策略,修改一个其中一个策略,并不会影响其他的策略,都可以单独使用。当然这只是个简单的范例,只为了说明。

策略模式比较明显的特性就是可以减少 if 语句或者 switch 语句。


职责链模式

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

例子

function order(options) {
  return {
    next: (callback) => callback(options),
  }
}

function order500(options) {
  const { orderType, pay } = options
  if (orderType === 1 && pay === true) {
    console.log('500 元定金预购, 得到 100 元优惠券')
    return {
      next: () => {},
    }
  } else {
    return {
      next: (callback) => callback(options),
    }
  }
}

function order200(options) {
  const { orderType, pay } = options
  if (orderType === 2 && pay === true) {
    console.log('200 元定金预购, 得到 50 元优惠券')
    return {
      next: () => {},
    }
  } else {
    return {
      next: (callback) => callback(options),
    }
  }
}

function orderCommon(options) {
  const { orderType, stock } = options
  if (orderType === 3 && stock > 0) {
    console.log('普通购买, 无优惠券')
    return {}
  } else {
    console.log('库存不够, 无法购买')
  }
}

order({
  orderType: 3,
  pay: true,
  stock: 500,
})
  .next(order500)
  .next(order200)
  .next(orderCommon)
// 打印出 “普通购买, 无优惠券”

上面的代码,对 order 相关的进行了解耦,order500,order200、orderCommon 等都是可以单独调用的。

链接: https://www.fly63.com/article/detial/4803

js设计模式之单例模式,javascript如何将一个对象设计成单例

单例模式是我们开发中一个非常典型的设计模式,js单例模式要保证全局只生成唯一实例,提供一个单一的访问入口,单例的对象不同于静态类,我们可以延迟单例对象的初始化,通常这种情况发生在我们需要等待加载创建单例的依赖。

前端设计模式:从js原始模式开始,去理解Js工厂模式和构造函数模式

工厂模式下的对象我们不能识别它的类型,由于typeof返回的都是object类型,不知道它是那个对象的实例。另外每次造人时都要创建一个独立的person的对象,会造成代码臃肿的情况。

JavaScript设计模式_js实现建造者模式

建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象

html和xhtml,DOCTYPE和DTD,标准模式和兼容模式

主要涉及知识点: HTML与XHTML,HTML与XHTML的区别,DOCTYPE与DTD的概念,DTD的分类以及DOCTYPE的声明方式,标准模式(Standard Mode)和兼容模式(Quircks Mode),标准模式(Standard Mode)和兼容模式(Quircks Mode)的区别

前端四种设计模式_JS常见的4种模式

JavaScript中常见的四种设计模式:工厂模式、单例模式、沙箱模式、发布者订阅模式

javascript 策略模式_理解js中的策略模式

javascript 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句,策略模式提供了开放-封闭原则,使代码更容易理解和扩展, 策略模式中的代码可以复用。

javascript观察者模式_深入理解js中的观察者模式

javascript观察者模式又叫发布订阅模式,观察者模式的好处:js观察者模式支持简单的广播通信,自动通知所有已经订阅过的对象。存在一种动态关联,增加了灵活性。目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

Vue中如何使用方法、计算属性或观察者

熟悉 Vue 的都知道 方法methods、计算属性computed、观察者watcher 在 Vue 中有着非常重要的作用,有些时候我们实现一个功能的时候可以使用它们中任何一个都是可以的

我最喜欢的 JavaScript 设计模式

我觉得聊一下我爱用的 JavaScript 设计模式应该很有意思。我是一步一步才定下来的,经过一段时间从各种来源吸收和适应直到达到一个能提供我所需的灵活性的模式。让我给你看看概览,然后再来看它是怎么形成的

Flutter 设计模式 - 简单工厂

在围绕设计模式的话题中,工厂这个词频繁出现,从 简单工厂 模式到 工厂方法 模式,再到 抽象工厂 模式。工厂名称含义是制造产品的工业场所,应用在面向对象中,顺理成章地成为了比较典型的创建型模式

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!