JavaScript设计模式

JavaScript设计模式

设计原则

S O L I D 五大设计原则

  • S———单一职责原则

  • O———开放封闭原则

  • L———李氏置换原则

  • I———-接口独立原则

  • D———-依赖导致原则

S———单一职责原则

  • 一个程序只做好一件事
  • 如果功能过于复杂就拆分开,每个部分保持独立

O———开放封闭原则

  • 对扩展开放,对修改封闭
  • 增加需求时,扩展新代码,而非修改已有代码
  • 这是软件设计的终极目标

L———李氏置换原则

  • 子类能覆盖父类
  • 父类能出现的地方,子类就能出现
  • JS中使用较少(弱类型 & 继承使用较少)

I———-接口独立原则

  • 保持接口的单一独立,避免出现“胖接口”
  • JS 中没有接口(typescript 例外),使用较少
  • 类似于单一职责原则,这里更关注接口

D———-依赖导致原则

  • 面向接口编程,依赖于抽象而不依赖于具体
  • 使用方只关注接口而不关注具体类的实现
  • JS 中使用较少(没有接口 & 弱类型)

设计模式分类

创建型

  • 工厂模式(工厂方法模式,抽象工厂模式,建造者模式)
  • 单例模式
  • 原型模式

结构型

  • 适配器模式
  • 装饰器模式
  • 代理模式
  • 外观模式
  • 桥接模式
  • 组合模式
  • 享元模式

行为型

  • 策略模式
  • 模板方法模式
  • 观察者模式
  • 迭代器模式
  • 职责链模式
  • 命令模式
  • 备忘录模式
  • 状态模式
  • 访问者模式
  • 中介者模式
  • 解释器模式

工厂模式

工厂模式是为了解决多个类似对象声明的问题;也就是为了解决实例化对象产生重复的问题。

  • 将new操作单独封装
  • 遇到new时,考虑是否该是否使用工厂模式

代码演示

class Product{
  constructor(name){
    this.name = name
  }
  init(){alert('init')}
  fun1(){alert('fun1')}
  fun2(){alert('fun2')}
}

class Creator{
  create(name){
    return new Product(name)
  }
}

//test
let create = new Creator()
let p = create.create('p1')
p.init()
p.fun1()

//init
//fun1

单例模式

单例模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一变量进行访问。

单例模式是一个用来划分命名空间并将一批属性和方法组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次。

代码演示

//单例模式
let Singleton = function (name) {
  this.name = name
}

Singleton.prototype.getName = function () {
  return this.name
}

//获取实例对象
let getInstance = (function () {
  let instance = null
  return function (name) {
    if(!instance){
      instance = new Singleton(name)
    }
    return instance
  }
})()

//测试单例模式的实例
let a = getInstance('aa')
let b = getInstance('bb')

console.log(a === b)  //true
console.log(a.getName())  //aa
console.log(b.getName())  //aa
//JQuery只有一个'$'
if(window.jQuery != null){
    return window.jQuery
}else{
    //初始化
}
//模拟登录框
class LoginForm{
  constructor(){
    this.state = 'hide'
  }
  show(){
    if(this.state === 'show'){
      alert('已经显示')
      return 
    }
    this.state = 'show'
    console.log('登录框已显示')
  }
  hide(){
    if(this.state === 'hide'){
      alert('已经隐藏')
      return 
    }
    this.state = 'hide'
    console.log('登录框已隐藏')
  }
}
LoginForm.getInstance = (function () {
  let instance
  return function(){
    if(!instance){
      instance = new LoginForm()
    }
    return instance
  }
})()
//test
let login1 = LoginForm.getInstance()
login1.show()
let login2 = LoginForm.getInstance()
login2.hide()
console.log('login1 === login2',login1 === login2)

适配器模式

解决两个软件实体间的接口不兼容的问题,对不兼容的部分进行适配

将一个类(对象)的接口(方法或属性)转化成另一个接口,以满足用户需求,使类(对象)之间接口的不兼容性问题通过适配器方法得以解决

代码演示

class Adaptee{
  specificRequest(){
    return '德国标准的插头'
  }
}

class Target{
  constructor(){
    this.adaptee = new Adaptee()
  }
  request(){
    let info = this.adaptee.specificRequest()
    return `${info} -> 转换器 -> 中国标准的插头`
  }
}

//text
let target = new Target()
target.request()
//封装旧接口
//自己封装的 ajax ,使用方式如下:
ajax({
  url:'/getData',
  type:'Post',
  dataType:'json',
  data:{
    id:"123"
  }
})
.done(function () {})

//但因为历史原因,代码中去都是
//$.ajax({...})

//做一层适配器
var $ = {
  ajax:function (options) {
    return this.ajax(options)
  }
}

装饰器模式

装饰者(decorator)模式能够在不改变对象自身的基础上,动态的给某个对象添加额外的职责,不会影响原有接口的功能。

代码演示

@testDec
class Demo{}

function testDec(target) {
  target.isDec = true
}
alert(Demo.isDec)

代理模式

  • 使用者无权访问目标对象
  • 中间加代理,通过代理做授权和控制

代码演示

class ReadImg{
  constructor(fileName){
    this.fileName = fileName
    this.loadFromDisk()
  }
  display(){
    console.log('display...' + this.fileName)
  }
  loadFromDisk(){
    console.log('loading...' + this.fileName)
  }
}

class ProxyImg{
  constructor(fileName){
    this.realImg = new ReadImg(fileName)
  }
  display(){
    this.realImg.display()
  }
}

//test
let proxyImg = new proxyImg('1.png')
proxyImg.display()

//明星和经纪人
//明星
let star = {
  name:'鞠婧祎',
  age:25,
  phone:'star : 13900001111'
}
//经纪人
let agent = new Proxy(star,{
  get:function (target,key) {
    if(key === 'phone'){
      //返回经纪人自己的电话
      return 'agent : 16899997777'
    }
    if(key === 'price'){
      //明星不报价,经纪人报价
      return 120000
    }
    return target[key]
  },
  set:function (target,key,val){
    if(key === 'customPrice'){
      if(val < 100000){
        //最低10w
        throw new Error('价格太低')
      }else{
        target[key] = val
        return true
      }
    }
  }
})

//test
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)
agent.customPrice = 150000
console.log('agent.customPrice',agent.customPrice)

外观模式

不符合单一职责原则和开放封闭原则,因此谨慎使用,不可滥用

代码演示

function bindEvent(elem,type,selector,fn) {
  if(fn == null){
    fn = selector
    selector = null
  }

  // ******
}

//调用
bindEvent(elem,'click','#div',fn)
bindEvent(elem,'click',fn)

观察者模式(最重要)

观察者模式,通常也被叫做 发布-订阅模式

当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他对象通知的问题

  • 一对多
  • 主题和观察者分离,不是主动触发而是被动监听,两者解耦
  • 符合开放封闭原则

代码演示

//主题,保存状态,状态变化之后触发所有观察者对象
class Subject{
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState(){
    return this.state
  }
  setState(state){
    this.state = state
    this.notifyAllObservers()
  }
  notifyAllObservers(){
    this.observers.forEach(observer => {
      observer.update()
    })
  }
  attach(observer){
    this.observer.push(observer)
  }
}

//观察者
class Observer{
  constructor(name,subject){
    this.name = name
    this.subject = subject
    this.subject.attach(this)
  }
  update(){
    console.log(`${this.name} update,state : ${this.subject.getState()}`)
  }
}

//测试
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
let o3 = new Observer('o3', s)

s.setState(1)
s.setState(2)
s.setState(3)

迭代器模式

  • 顺序访问一个集合
  • 使用者无需知道集合的内部结构(封装)
  • 迭代器对象和目标对象分离
  • 迭代器将使用者与目标对象隔离开
  • 符合开放封闭原则

代码演示

class Iterator{
  constructor(container){
  this.list = container.list
  this.index = 0
  }
  next(){
    if(this.hasNext()){
      return this.list[this.index++]
    }
    return null
  }
  hasNext(){
    if(this.index >= this.list.length){
      return false
    }
    return true
  }
}

class Container{
  constructor(list){
    this.list = list
  }
  //生成遍历器
  getIterator(){
    return new Iterator(this)
  }
}

let container = new Container([1,2,3,4,5,6])
let iterator = container.getIterator()
while(iterator.hasNext()){
  console.log(iterator.next())
}

——–分割线———

以下不是前端重点掌握的设计模式

状态模式

状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变,同时把事物的每种状态都封装成单独的类。

代码演示

import StateMachine from 'javascript-state-machine'
import $ from 'jquery'

//初始化状态机模型
let fsm = new StateMachine({
  init:'收藏',
  transitions: [
    {
      name:'doState',
      from:'收藏',
      to:'取消收藏'
    },
    {
      name:'deleteStore',
      from:'取消收藏',
      to:'收藏'
    }
  ],
  methods: {
   //监听执行收藏
   onDoStore:function () {
     alert('收藏成功')   //可以 post 请求
     updateText()
   },
   //监听取消收藏
   onDeleteStore:function () {
     alert('已经取消收藏')  //可以 post 请求
     updateText()
   }
  }
})

let $btn = $('#btn1')

//按钮点击事件
$btn.click(function () {
  if(fsm.is('收藏')){
    fsm.doStore()
  }else{
    fsm.deleteStore()
  }
})

//更新按钮的文案
function updateText() {
  $btn.text(fsm.state)
}

//初始化文案
updateText()

原型模式

原型模式是用来创建对象的一种模式。在以类为中心的语言中,要创建一个对象首先要指定这个对象的类型,然后实例化一个对象。使用原型模式创建对象时不必关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象。所以在前者中如果要根据一个对象创建多个相同的对象,我们需要先保存这个对象的所有属性信息,然后将属性信息设置到新创建的对象上,而在原型模式中我们只需要使用克隆就能完成同样的功能。

代码演示

//'Object.create' 用到了原型模式的思想 (虽然不是Java中的clone)

//基于一个原型创建一个对象
var prototype = {
  getName:function(){
    return this.first + ' ' + this.last
  },
  say:function () {
    console.log('hello')
  }
}

//基于原型创建 x
var x = Object.create(prototype)
x.first = 'A'
x.last = 'B'
console.log(x.getName())
x.say()

//基于原型创建 y
var y = Object.create(prototype)
y.first = 'C'
y.last = 'D'
console.log(y.getName)
y.say()

对比JS中的prototype

prototype 可以理解为 ES6 class 的一种底层原理

而 class 是实现面向对象的基础,并不是服务于某个模式

ES6普及后大家可能会忽略到 prototype

但是 Object.create() 会长久存在

桥接模式

  • 用于把抽象化与实现化解耦
  • 使得二者可以独立变化

代码演示

class Color{
  constructor(name){
    this.name = name
  }
}

class Shape{
  constructor(name,color){
    this.name = name
    this.color = color
  }
  draw(){
    console.log(`${this.color.name} ${this.name}`)
  }
}

//测试代码
let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle',red)
circle.draw()
let triangle = new Shape('triangle',yellow)
triangle.draw()

组合模式

  • 生成树形结构,表示“整体-部分”的关系
  • 让整体和部分都具有一致的操作方式
  • 整体和部分的数据结构也保持一致
  • 将整体和单个节点的操作抽象出来
  • 符合开放封闭原则

代码演示

  <div id="div" class="container">
    <p>123</p>
    <p>456</p>
  </div>
{
  tag:'div',
  attr:{
    id:'div1',
    className:'container'
  },
  children:{
    {
      tag:'p',
      attr:{},
      children:['123']
    },
    {
      tag:'p',
      attr:{},
      children:['456']
    }
  }
}

享元模式

  • 共享内存(主要考虑内存,而非效率)
  • 相同的数据,共享使用
  • 将相同的部分抽离出来
  • 符合开放封闭原则

代码演示

  <div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
    <!-- 无限下拉列表 -->
  </div>
//无限下拉列表,将事件代理到高层节点上
//如果都绑定到<a> 标签,对内存开销太大
let div1 = document.getElementById('div1')
div1.addEventListener('click',function (e) {
  let target = e.target
  if(e.nodeName === 'A'){
    alert(target.innerHTML)
  }
})

策略模式

  • 不同策略分开处理
  • 避免出现大量 if … else 或者 switch … case
  • 符合开放封闭原则

代码演示

class OrdinaryUser{
  buy(){
    console.log('普通用户购买')
  }
}

class MemberUser{
  buy(){
    console.log('会员用户购买')
  }
}

class VipUser{
  buy(){
    console.log('vip 用户购买')
  }
}

let u1 = new OrdinaryUser()
u1.buy()
let u2 = new MemberUser()
u2.buy()
let u3 = new VipUser()
u3.buy()

模板方法模式

代码演示

class Action{
  handle(){
    handle1()
    handle2()
    handle3()
  }
  handle1(){
    console.log('1')
  }
  handle2(){
    console.log('2')
  }
  handle3(){
    console.log('3')
  }
}

职责链模式

  • 一步操作可能分位多个职责角色来完成
  • 把这些角色都分开,然后用一个链串起来
  • 将发起者和各个处理者进行隔离

代码演示

//请假审批,需要组长审批,经理审批,最后总监审批

class Action{
  constructor(name){
    this.name = name
    this.nextAction = null
  }
  setNextAction(action){
    this.nextAction = action
  }
  handle(){
    console.log(`${this.name}审批`)
    if(this.nextAction != null){
      this.nextAction.handle()
    }
  }
}

let a1 = new Action('组长')
let a2 = new Action('经理')
let a3 = new Action('总监')
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

命令模式

  • 执行命令时,发布者和执行者分开
  • 中间加入命令对象,作为中转站
  • 符合开放封闭原则

代码演示

class Receiver{
  exec(){
    console.log('执行')
  }
}

class Command{
  constructor(receiver) {
    this.receiver = receiver
  }
  cmd(){
    console.log('触发命令')
    this.receiver.exec()
  }
}

class Invoker{
  constructor(command){
    this.command = command
  }
  invoke(){
    console.log('开始')
    this.command.cmd()
  }
}

//士兵
let soldier = new Receiver()
//小号手
let trumpeter = new Command(soldier)
//将军
let general = new Invoker(trumpeter)

general.invoke()

备忘录模式

  • 随时记录一个对象的状态变化
  • 随时可以恢复之前的某个状态(如撤销功能)

代码演示

//备忘录
class Memento{
  constructor(content){
    this.content = content
  }
  getContent(){
    return this.content
  }
}

//备忘列表
class CareTaker{
  constructor(){
    this.list = []
  }
  add(memento){
    this.list.push(memento)
  }
  get(index){
    return this.list[index]
  }
}

//编辑器
class Editor{
  constructor(){
    this.content = null
  }
  setContent(content){
    this.content = content
  }
  getContent(){
    return this.content
  }
  saveContentToMemento(){
    return new Memento(this.content)
  }
  getContentFromMemento(memento){
    this.content = memento.getContent()
  }
}

//测试代码
let editor = new Editor()
let careTaker = new CareTaker()

editor.setContent('111')
editor.setContent('222')
careTaker.add(editor.saveContentToMemento())  //将当前内容备份
editor.setContent('333')
careTaker.add(editor.saveContentToMemento())  //将当前内容备份
editor.setContent('444')

console.log(editor.getContent())
editor.getContentFromMemento(careTaker.get(1))   //撤销
console.log(editor.getContent())
editor.getContentFromMemento(careTaker.get(0))   //撤销
console.log(editor.getContent())

中介者模式

  • 将各关联对象通过中介者隔离
  • 符合开放封闭原则

代码演示

class A{
  constructor(){
    this.number = 0
  }
  setNumber(num,m){
    this.number = num
    if(m){
      m.setB()
    }
  }
}

class B{
  constructor(){
    this.number = 0
  }
  setNumber(num,m){
    this.number = num
    if(m){
      m.setA()
    }
  }
}

class Mediator{
  constructor(a,b) {
    this.a = a
    this.b = b
  }
  setA(){
    let number = this.b.number
    this.a.setNumber(number * 100)
  }
  setB(){
    let number = this.a.number
    this.b.setNumber(number / 100)
  }
}

//测试
let a = new A()
let b = new B()
let m = new Mediator(a,b)
a.setNumber(100,m)
console.log(a.number,b.number)
b.setNumber(100,m)
console.log(a.number,b.number)

访问者模式和解释器模式不介绍了,前端应用场景很少

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值