JavaScript设计模式与开发实践

  最近在研读了腾讯AlloyTeam前端团队,高级工程师曾探编写的《JavaScript设计模式与开发实践》,所有设计模式的实现都遵循一条原则,即“找出程序中变化的地方,并将变化封装起来”。一个程序的设计总是可以分为可变的部分和不变的部分。当我们找出可变的部分,并且把这部分封装起来,那么剩下的就是不变和稳定的部分。

  JavaScript没有提供传统面向对象语言中的类式继承,而是通过原型委托的方式来实现对象与对象之间的继承。JavaScript也没有在语言层面提供对抽象类和接口的支持。所以在JavaScript用设计模式编写代码的时候,要跟传统面向对象语言加以区分。

1、单例模式

  单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全军缓存、浏览器中的window对象等。

<html>
  <head>
    <title>惰性单例-点击登录弹出登录浮窗</title>
  </head>
  <body>
    <button id="loginBtn">登录</button>
    <script>
      // 创建实例对象的职责
      let createLoginLayer = function () {
        let div = document.createElement('div')
        div.innerHTML = '我是登录浮窗'
        div.style.display = 'none'
        document.body.appendChild(div)
        return div
      }
      let createIframeLayer =  function () {
        let iframe = document.createElement('iframe')
        document.body.appendChild(iframe)
        return iframe
      }
      // 管理单例的职责
      let getSingle = function (fn) {
        let result
        return function () {
          return result || (result = fn.apply(this, arguments))
        }
      }
      // 创建div浮窗
      let createSingleLoginLayer = getSingle(createLoginLayer)
      // 点击多次都只会创建一个新的登录浮层div
      document.getElementById('loginBtn').onclick = function () {
        let loginLayer = createSingleLoginLayer()
        loginLayer.style.display = 'block';
      }
    </script>
  </body>
</html>

2、策略模式

  策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
  策略模式的目的就是将算法的使用和算法的实现分离开来。说的更详细点就是:定义一些列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某一个进行计算。

// 普通实现
let calculateBonusCommon = (performanceLevel, salary) => {
  if (performanceLevel === 'S') {
    return salary * 4
  }
  if (performanceLevel === 'A') {
    return salary * 3
  }
  if (performanceLevel === 'B') {
    return salary * 2
  }
}
console.log(calculateBonusCommon('B', 20000))
console.log(calculateBonusCommon('S', 50000))

// 策略模式实现
// 策略类
let strategies = {
  'S': (salary) => salary * 4,
  'A': (salary) => salary * 3,
  'C': (salary) => salary * 2
}
// 环境类
let calculateBonus = (level, salary) => strategies[level](salary)
console.log(calculateBonus('S', 20000))
console.log(calculateBonus('A', 10000))

通过使用策略模式重构代码,我们消除了原程序中大片的条件分支语句。

3、代理模式

  代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
  代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。

<html>
  <head>
    <title>虚拟代理实现图片预加载</title>
  </head>
  <body>
    <script>
      // 加载图片
      let myImage = (function () {
        let imgNode = document.createElement('img')
        document.body.appendChild(imgNode)
        return {
          setSrc: function (src) {
            imgNode.src = src
          }
        }
      })()
      // 代理对象proxyImage
      let proxyImage = (function () {
        let img = new Image
        img.onload = function () {
          myImage.setSrc(this.src)
        }
        return {
          setSrc: function (src) {
            myImage.setSrc('./loading.gif')
            img.src = src
          }
        }
      })()
      proxyImage.setSrc('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1521956229659&di=36a2ea375f48e8328b3cab79e8b1ea0e&imgtype=0&src=http%3A%2F%2Ff0.topitme.com%2F0%2Fa9%2F3e%2F1164210455aae3ea90o.jpg')
    </script>
  </body>
</html>

  如果有一天我们不再需要预加载,那么就不再需要代理对象,可以选择直接请求本体。其中关键是代理对象和本体都对外提供了setSrc方法,在客户看来,代理对象和本体是一致的。

// 乘积
let mult = function () {
  console.log('开始计算乘积')
  let a = 1
  for (let i = 0, l = arguments.length; i < l; i++) {
    a *= arguments[i]
  }
  return a
}
// 加和
let plus = function () {
  console.log('开始计算加和')
  let a = 1
  for (let i = 0, l = arguments.length; i < l; i++) {
    a += arguments[i]
  }
  return a
}
// 减法
let subtraction = function () {
  console.log('开始计算减法')
  let a = 1
  for (let i = 0, l = arguments.length; i < l; i++) {
    a -= arguments[i]
  }
  return a
}
// 缓存代理函数
let proxyMult = (function () {
  let cache = []
  return function () {
    let args = Array.prototype.join.call(arguments, ',')
    if (args in cache) {
      return cache[args]
    }
    return cache [args] = mult.apply(this, arguments)
  }
})()
console.log(proxyMult(1, 2, 3, 4))
console.log(proxyMult(1, 2, 3, 4))
console.log(proxyMult(1, 2, 3, 4, 5))

// 高阶函数动态创建缓存代理的工厂
let createProxyFactory = function (fn) {
  let cache = []
  return function () {
    let args = Array.prototype.join.call(arguments, ',')
    for (args in cache) {
      return cache[args]
    }
    return cache[args] = fn.apply(this, arguments)
  }
}
let proxyPlus = createProxyFactory(plus)
let proxySubtrsction = createProxyFactory(subtraction)
console.log(proxyPlus(1, 2, 3, 4))
console.log(proxyPlus(1, 2, 3, 4))
console.log(proxySubtrsction(10, 3, 4))
console.log(proxySubtrsction(10, 3, 4))

4、迭代器模式

  迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

let each = function (ary, callback) {
  for (let i = 0, l = ary.length; i < l; i++) {
    callback.call(ary[i], i, ary[i])
  }
}
each([1, 2, 3, 4], function(i, n) {
  console.log([i, n])
})

5、发布-订阅模式

  发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,我们一般用事件模型来代替传统的发布-订阅模式。
下面看实现发布-订阅模式的步骤:

  • 首先要指定好谁充当发布者。
  • 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者。
  • 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数
// 全局发布-订阅对象
let Event = (function () {
  // 缓存列表,存放订阅者的回调函数
  let clientList = {}
  let listen, trigger, remove
  // 增加订阅者
  listen = (key, fn) => {
    if (!clientList[key]) {
      clientList[key] = []
    }
    clientList[key].push(fn)
  },
  // 发布消息
  trigger = (...value) => {
    let key = Array.prototype.shift.call(value)
    let fns = clientList[key]
    // 如果没有绑定的对应的消息
    if (!fns || fns.length === 0) {
      return false
    }
    for ( let i = 0, fn; fn = fns[i++]; ) {
      fn.apply(this, value)
    }
  },
  // 取消订阅事件
  remove = (key, fn) => {
    let fns = clientList[key]
    // 如果key对应的消息没有被人订阅,则直接返回
    if (!fns) {
      return false
    }
    if (!fn) { // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
      fns && (fns.length = 0)
    } else {
      for (let l = fns.length - 1; l >= 0; l--) {
        let _fn = fns[l]
        if (_fn === fn) {
          fns.splice(l, 1) // 删除订阅者的回调函数
        }
      }
    }
  }
  return {
    listen,
    trigger,
    remove
  }
})()
// 小明订阅消息
Event.listen('squareMeter88', fn1 = function (price, squareMeter) {
  console.log('小明先生:')
  console.log('price = ' + price)
  console.log('squareMeter = ' + squareMeter)
})
// 小红订阅消息
Event.listen('squareMeter88', fn2 = function (price, squareMeter) {
  console.log('小红小姐:')
  console.log('price = ' + price)
  console.log('squareMeter = ' + squareMeter)
})
// 售楼处发布消息
Event.trigger('squareMeter88', 10000, 88)
Event.remove('squareMeter88', fn1)
Event.trigger('squareMeter88', 15000, 88)

6、命令模式

  命令模式中的命令(command)指的是一个执行某些特定事情的指令。
  命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

let button1 = document.getElementById('button1')
let button2 = document.getElementById('button2')
let button3 = document.getElementById('button3')
let MenuBar = {
  refresh: function() {
    console.log('刷新菜单界面')
  }
}
let SubMenu = {
  add: function () {
    console.log('增加子菜单')
  },
  del: function () {
    console.log('删除子菜单')
  }
}
// 接收者被封闭在闭包产生的环境中,执行命令的操作可以更加简单,仅仅执行回调函数即可
let RefreshMenuBarCommand = function (receiver) {
  return {
    execute: function () {
      receiver.refresh()
    }
  }
}
let AddSubMenuBarCommand = function (receiver) {
  return {
    execute: function () {
      receiver.add()
    }
  }
}
let DelSubMenuBarCommand = function (receiver) {
  return {
    execute: function () {
      receiver.del()
    }
  }
}
// setCommand函数负责往按钮上面安装命令
let setCommand = function (button, command) {
  button.onclick = function () {
    command.execute()
  }
}

let refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
setCommand(button1, refreshMenuBarCommand)
let addSubMenuBarCommand = AddSubMenuBarCommand(SubMenu)
setCommand(button2, addSubMenuBarCommand)
let delSubMenuBarCommand = DelSubMenuBarCommand(SubMenu)
setCommand(button2, delSubMenuBarCommand)

宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。

let closeDoorCommand = {
  execute: function () {
    console.log('关门')
  }
}

let openPcCommand = {
  execute: function () {
    console.log('开电脑')
  }
}
let openQQCommand = {
  execute: function () {
    console.log('登录QQ')
  }
}
let MacroCommand = function () {
  return {
    commandsList: [],
    add: function (command) {
      this.commandsList.push(command)
    },
    execute: function() {
      for (let i =0, command; command = this.commandsList[i++];) {
        command.execute()
      }
    }
  }
}
let macroCommand = MacroCommand()
macroCommand.add(closeDoorCommand)
macroCommand.add(openPcCommand)
macroCommand.add(openQQCommand)
macroCommand.execute()

7、组合模式

  组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的。组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。

// 组合模式-扫描文件夹
// 文件夹
let Folder = function (name) {
  this.name = name
  this.files = []
}
Folder.prototype.add = function (file) {
  this.files.push(file)
}
Folder.prototype.scan = function () {
  console.log('开始扫描文件夹:' + this.name)  
  for(let i = 0, file; file = this.files[i++];) {
    file.scan()
  }
}

// 文件
let File = function (name) {
  this.name = name
}
File.prototype.add = function () {
  throw new Error('文件下面不能再添加文件')
}
File.prototype.scan = function () {
  console.log('开始扫描文件:' + this.name)
}

// 创建文件夹
let folder = new Folder('学习资料')
let folder1 = new Folder('Javascript')
let folder2 = new Folder('JQuery')
// 创建文件
let file1 = new File('Javascript 设计模式与开发实践')
let file2 = new File('精通JQuery')
let file3 = new File('重构与模式')

folder1.add(file2)
folder2.add(file3)

folder.add(file1)
folder.add(file2)
folder.add(file3)
folder.add(folder1)
folder.add(folder2)

folder.scan()

8、模板方法模式

  模板方法模式是一种只需使用继承就可以实现的非常简单的模式。模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来实现。这也很好地体现了泛化的思想。

let Beverage = function () {}
Beverage.prototype.boilWater = function () {
  console.log('把水煮沸')
}
Beverage.prototype.brew = function () {
  throw new Error('子类必须重写brew方法')
}
Beverage.prototype.pourInCup = function () {
  throw new Error('子类必须重写pourInCup方法')
}
Beverage.prototype.addCondiments = function () {
  throw new Error('子类必须重写addCondiments方法')
}
// 钩子方法
Beverage.prototype.customerWantsCondiments = function () {
  return true // 默认需要调料
}
// 模板方法
Beverage.prototype.init = function () { 
  this.boilWater()
  this.brew()
  this.pourInCup()
  // 如果挂钩返回true。则需要调料
  if (this.customerWantsCondiments()) {
    this.addCondiments()
  }
}

// 泡茶
let Tea = function () {}
Tea.prototype = new Beverage()
Tea.prototype.brew = function () {
  console.log('用沸水浸泡茶叶')
}
Tea.prototype.pourInCup = function () {
  console.log('把茶倒进杯子')
}
Tea.prototype.addCondiments = function () {
  console.log('加柠檬')
}
Tea.prototype.customerWantsCondiments = function () {
  return window.confirm('请问需要调料吗?')
}
let tea = new Tea()
tea.init()

//泡咖啡
let Coffee = function () {}
Coffee.prototype = new Beverage()
Coffee.prototype.brew = function () {
  console.log('用沸水冲泡咖啡')
}
Coffee.prototype.pourInCup = function () {
  console.log('把咖啡倒进杯子')
}
Coffee.prototype.addCondiments = function () {
  console.log('加牛奶和糖')
}
let coffee = new Coffee()
coffee.init()

利用好莱坞原则,下面的代码可以达到和继承一样的效果

let Beverage = function (param) {
  let boilWater = function () {
    console.log('把水煮沸')
  }
  let brew = param.brew || function () {
    throw new Error('必须传递brew方法')
  }
  let pourInCup = param.pourInCup || function () {
    throw new Error('必须传递pourInCup方法')
  }
  let addCondiments = param.addCondiments || function () {
    throw new Error('必须传递addCondiments方法')
  }
  let customerWantsCondiments = param.customerWantsCondiments ? true : false
  let F = function () {}
  // 模板方法
  F.prototype.init = function () { 
    boilWater()
    brew()
    pourInCup()
    if( customerWantsCondiments) {
      addCondiments()
    }
  }
  return F
}
 let Coffee = Beverage({
   brew: function () {
     console.log('用沸水冲泡咖啡')
   },
   pourInCup: function () {
     console.log('把咖啡倒入杯子')
   },
   addCondiments: function () {
     console.log('加糖和牛奶')
   }
 })
 let coffee = new Coffee()
 coffee.init()

 let Tea = Beverage ({
   brew: function () {
     console.log('用沸水泡茶')
   },
   pourInCup: function () {
     console.log('把茶倒入杯子')
   },
   customerWantsCondiments: false
 })
 let tea = new Tea()
 tea.init()

9、享元模式

  享元模式是一种用于性能优化的模式。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
  享元模式要求将对象的属性划分为内部状态和外部状态,把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并存储在外部。

<html>
  <head>
    <title>享元模式文件上传</title>
  </head>
  <body>
    <script>
      // uploadType是内部状态,fileName和fileSize是根据场景而变化,每个文件fileName和
      // fileSize都不一样,fileName和fileSize没有办法被共享,它们只能被划分为外部状态
      let Upload = function (uploadType) {
        this.uploadType = uploadType
      }
      Upload.prototype.delFile = function (id) {
        uploadManager.setExternalState(id, this)
        if (this.fileSize < 3000) {
          return this.dom.parentNode.removeChild(this.dom)
        }
        if (window.confirm('确定要删除该文件吗?' + this.fileName)) {
          return this.dom.parentNode.removeChild(this.dom)
        }
      }

      // 工厂进行对象实例化,如果某种内部状态对应的共享对象已经被创建过,那么直接返回
      // 这个对象,否则创建一个新的对象
      let UploadFactory = (function () {
        let createFlyWeightObjs = {}
        return {
          create: function (uploadType) {
            if (createFlyWeightObjs[uploadType]) {
              return createFlyWeightObjs[uploadType]
            }
            return createFlyWeightObjs[uploadType] = new Upload(uploadType)
          }
        }
      })()

      // 管理器封装外部状态
      let uploadManager = (function () {
        // 保存所有upload对象的外部状态
        let uploadDatabase = {}
        return {
          add: function (id, uploadType, fileName, fileSize) {
            let flyWeightObj = UploadFactory.create(uploadType)
            let dom = document.createElement('div')
            dom.innerHTML = '<span>文件名称:' + fileName + ',文件大小:' + fileSize + '</span>' +
              '<button class="delFile">删除</button>'
            dom.querySelector('.delFile').onclick = function () {
              flyWeightObj.delFile(id)
            }
            document.body.appendChild(dom)
            uploadDatabase[id] = {
              fileName: fileName,
              fileSize: fileSize,
              dom: dom
            }
            return flyWeightObj
          },
          setExternalState: function (id, flyWeightObj) {
            let uploadData = uploadDatabase[id]
            for (let i in uploadData) {
              flyWeightObj[i] = uploadData[i]
            }
          }
        }
      })()

      // 触发上传动作
      let id = 0
      window.startUpload = function (uploadType, files) {
        for (let i = 0, file; file = files[i++];) {
          let uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize)
        }
      }

      startUpload('plugin', [
        {
          fileName: '1.txt',
          fileSize: 1000
        },
        {
          fileName: '2.html',
          fileSize: 3000
        },
        {
          fileName: '3.txt',
          fileSize: 5000
        }
      ])

      startUpload('flash', [
        {
          fileName: '4.txt',
          fileSize: 1000
        },
        {
          fileName: '5.html',
          fileSize: 3000
        },
        {
          fileName: '6.txt',
          fileSize: 5000
        }
      ])
    </script>
  </body>
</html>

10、职责链模式

  职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

let order500 = function (orderType, pay, stock) {
  if (orderType === 1 && pay) {
    console.log('500元定金预购,得到100优惠券')
  } else {
    return 'nextSuccessor'
  }
}
let order200 = function (orderType, pay, stock) {
  if(orderType === 2 && pay) {
    console.log('200元定金预购,得到50优惠券')
  } else {
    return 'nextSuccessor'
  }
}
let orderNomal = function (orderType, pay, stock) {
  if (stock > 0) {
    console.log('普通购买,无优惠券')
  } else {
    console.log('手机库存不足')
  }
}

// 用AOP实现职责链
Function.prototype.after = function (fn) {
  let self = this
  return function () {
    let ret = self.apply(this, arguments)
    if (ret === 'nextSuccessor') {
      return fn.apply(this, arguments)
    }
    return ret
  }
}

let order = order500.after(order200).after(orderNomal)
order(1, true, 500)
order(2, true, 500)
order(3, true, 500)
order(1, false, 0)

11、中介者模式

  中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各个对象之间耦合松散,而且可以独立地改变它们之间的交互。

/**
 * @description player对象的原型方法中,不负责具体的执行逻辑,而是把操作转交给中介者对象。
 * @param {any} name 
 * @param {any} teamColor 
 */
function Player (name, teamColor) {
  this.state = 'live' // 玩家状态
  this.name = name // 角色名字
  this.teamColor = teamColor // 队伍颜色
}

// 玩家胜利
Player.prototype.win = function () {
  console.log('winner: ' + this.name)
}

// 玩家失败
Player.prototype.lose = function () {
  console.log('loser: ' + this.name)
}

// 玩家死亡
Player.prototype.die = function () {
  this.state = 'dead'
  // 给中介者发送消息,玩家死亡
  playerDirector.ReceiveMessage('playerDead', this)
}

// 移除玩家
Player.prototype.remove = function () {
  // 给中介者发送消息,移除一个玩家
  playerDirector.ReceiveMessage('removePlayer', this)
}

// 玩家换队
Player.prototype.changeTeam = function (color) {
  // 给中介者发送消息,玩家换队
  playerDirector.ReceiveMessage('changeTeam', this, color)
}

/**
 * @description 工厂函数
 */
let playerFactory = function (name, teamColor) {
  // 创造一个新的玩家对象
  let newPlayer = new Player(name, teamColor)
  // 给中介者发送消息,新增玩家
  playerDirector.ReceiveMessage('addPlayer', newPlayer)
  return newPlayer
}
/**
 * @description 中介者
 */
let playerDirector = (function () {
  // 保存所有玩家
  let players = {}
  // 中介者可以执行的操作
  let operations = {}
  /******************* 新增一个玩家 ******************/
  operations.addPlayer = function (player) {
    // 玩家队伍的颜色
    let teamColor = player.teamColor
    // 如果该颜色的玩家还没有成立队伍,则新成立一个队伍
    players[teamColor] = players[teamColor] || []
    // 添加玩家进队伍
    players[teamColor].push(player)
  }
  /******************* 移除一个玩家 ******************/
  operations.removePlayer = function (player) {
    // 玩家的队伍颜色
    let teamColor = player.teamColor
    // 该队伍的所有成员
    let teamPlayers = players[teamColor] || []
    // 遍历删除
    for (let i = teamPlayers.length - 1; i >=0; i--) {
      if(teamPlayers[i] === player) {
        teamPlayers.splice(i, 1)
      }
    }
  }
  /******************* 玩家换队 ******************/
  operations.changeTeam = function (player, newTeamColor) {
    operations.removePlayer(player)
    player.teamColor = newTeamColor
    operations.addPlayer(player)
  }
  /******************* 玩家死亡 ******************/
  operations.playerDead = function (player) {
    // 玩家的队伍颜色
    let teamColor = player.teamColor
    // 玩家所在队伍
    let teamPlayers = players[teamColor]
    let all_dead = true
    for (let i = 0, player; player = teamPlayers[i++];) {
      if (player.state !== 'dead') {
        all_dead = false
        break
      }
    }
    if (all_dead) {
      // 本队所有玩家都输了
      for(let i = 0, player; player = teamPlayers[i++];) {
        player.lose()
      }
      for (let color in players) {
        if (color !== teamColor) {
          // 其他队伍的玩家
          let teamPlayers = players[color]
          // 其他队伍所有玩家胜利
          for (let i = 0, player; player = teamPlayers[i++];) {
            player.win()
          }
        }
      }
    }
  }
  /********* 负责接收player对象发送的消息 *********/
  let ReceiveMessage = function () {
    let message = Array.prototype.shift.call(arguments)
    operations[message].apply(this, arguments)
  }
  return {
    ReceiveMessage: ReceiveMessage
  }
})()

// 红队
let player1 = playerFactory('皮蛋', 'red')
let player2 = playerFactory('小乖', 'red')
let player3 = playerFactory('小强', 'red')
let player4 = playerFactory('小雪', 'red')
let player5 = playerFactory('小明', 'red')

// 蓝队
let player6 = playerFactory('黑妞', 'blue')
let player7 = playerFactory('兔头', 'blue')
let player8 = playerFactory('胖墩', 'blue')
let player9 = playerFactory('海盗', 'blue')
let player10 = playerFactory('大仙', 'blue')

// 黄队
let player11 = playerFactory('大牛', 'yellow')
let player12 = playerFactory('小王', 'yellow')
let player13 = playerFactory('小刘', 'yellow')
let player14 = playerFactory('小陈', 'yellow')
let player15 = playerFactory('小马', 'yellow')

player1.remove()
player2.changeTeam('yellow')
player3.die()
player4.die()
player5.die()

12、装饰者模式

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

Function.prototype.before = function (beforefn) {
  // 保存原函数的引用
  let _self = this
  // 返回包含了原函数好新函数的代理函数
  return function () {
    // 执行函数,且保证this不会被劫持,新函数接受的参数也会
    // 原封不动地传入原函数,新函数在原函数之前执行
    beforefn.apply(this, arguments)
    // 执行原函数并返回原函数的执行结果,并且保证this不被劫持
    return _self.apply(this, arguments)
  }
}

Function.prototype.after = function (afterfn) {
  let _self = this
  return function () {
    let ret = _self.apply(this, arguments)
    afterfn.apply(this, arguments)
    return ret
  }
}

window.onload = function () {
  console.log('1')
}
window.onload  = (window.onload() || function () {}).after(function () {
  console.log('2')
}).after(function () {
  console.log('3')
}).after(function () {
  console.log('4')
})

13、状态模式

  状态模式的定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
  状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。状态模式把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部。

<html>
  <body>
    <script>
      // 每个状态都必须实现buttonWasPressed方法
      let State = function () { }
      State.prototype.buttonWasPressed = function () {
        throw new Error('父类的buttonWasPressed方法必须被重写')
      }

      // OffLightState
      let OffLightState = function (light) {
        this.light = light
      }
      OffLightState.prototype = new State()
      OffLightState.prototype.buttonWasPressed = function () {
        console.log('弱光')
        // 切换状态到weakLightState
        this.light.setState(this.light.weakLightState)
      }
      // WeakLightState
      let WeakLightState = function (light) {
        this.light = light
      }
      WeakLightState.prototype = new State()
      WeakLightState.prototype.buttonWasPressed = function () {
        console.log('强光')
        this.light.setState(this.light.strongLightState)
      }

      // StrongLightState
      let StrongLightState = function (light) {
        this.light = light
      }
      StrongLightState.prototype = new State()
      StrongLightState.prototype.buttonWasPressed = function () {
        console.log('关灯')
        this.light.setState(this.light.offLightState)
      }

      // Light类
      let Light = function () {
        this.offLightState = new OffLightState(this)
        this.weakLightState = new WeakLightState(this)
        this.strongLightState = new StrongLightState(this)
        this.button = null
      }

      Light.prototype.init = function () {
        let button = document.createElement('button')
        let self = this
        this.button = document.body.appendChild(button)
        this.button.innerHTML = '开关'
        this.currState = this.offLightState
        this.button.onclick = function () {
          self.currState.buttonWasPressed()
        }
      }

      Light.prototype.setState = function (newState) {
        this.currState = newState
      }

      let light = new Light()
      light.init()
    </script>
  </body>
</html>

14、适配器模式

  适配器模式主要用来解决两个已有接口之间不匹配的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。

let googleMap = {
  show: function () {
    console.log('开始渲染谷歌地图')
  }
}
let baiduMap = {
  show: function () {
    console.log('开始渲染谷歌地图')
  }
}
let sosoMap = {
  display: function () {
    console.log('开始渲染搜搜地图')
  }
}
// 适配器模式
let sosoMapAdapter = {
  show: function () {
    return sosoMap.display()
  }
}
let renderMap = function (map) {
  if (map.show instanceof Function) {
    map.show()
  }
}
renderMap(googleMap)
renderMap(baiduMap)
renderMap(sosoMapAdapter)

猜你喜欢

转载自blog.csdn.net/qq_27626333/article/details/79807376