Skip to content

《JavaScript 设计模式与开发实践》读书笔记-设计模式

  • 笔记内容多摘录自《JavaScript 设计模式与开发实践》(曾探著),侵删。
  • 把不变的事物和变化的事物分离开来

一 单例模式

  • 确保只有一个实例,并提供全局访问
  • 区分创建对象和管理单例的职责
  • 惰性单例

设计原则:单一职责

应用:全局缓存、线程池

js
var getSingle = function(fn) {
  var result
  return function() {
    return result || (result = fn.apply(this, arguments))
  }
}

二 策略模式

定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换

  • 将算法的使用与算法的实现分离
  • 策略类(Strategies),环境类(Context)

设计原则:符合开闭原责,违背最少知识原则

应用:表单验证

js
var strategies = {
  A: function(salary) {
    return salary * 4
  },
  B: function(salary) {
    return salary * 3
  },
  C: function(salary) {
    return salary * 2
  },
}
var context = function(func, salary) {
  return func(salary)
}

context(strategies.A, 100)

三 代理模式

为对象提供一个代用品或占位符,以便控制对它的访问

  • 代理和本体接口的一致性

设计原则:符合开闭原责、单一职责

虚拟代理

js
var myImage = (function() {
  var imgNode = document.createElement('img')
  document.body.appendChild(imgNode)
  return function(src) {
    imgNode.src = src
  }
})()
var proxyImage = (function() {
  var img = new Image()
  img.onload = function() {
    myImage(this.src)
  }
  return function(src) {
    myImage('loading.gif')
    img.src = src
  }
})()
proxyImage('1.jpg')

缓存代理

js
var createProxyFactory = function(fn) {
  var cache = {}
  return function() {
    var args = Array.prototype.join.call(arguments, ',')
    if (args in cache) {
      return cache[args]
    }
    return (cache[args] = fn.apply(this, arguments))
  }
}

其他代理模式

  • 防火墙代理
  • 远程代理
  • 保护代理
  • 智能引用代理
  • 写时复制代理

四 迭代器模式

提供一种方法顺序访问一个聚合对象的各个元素

内部迭代器

js
var each = function(ary, callback) {
  for (var i = 0, l = ary.length; i < l; i++) {
    callback.call(ary[i], i, ary[i])
  }
}

each([1, 2, 3], function(i, n) {
  alert([i, n])
})

外部迭代器

js
var Iterator = function(obj) {
  var current = 0
  var next = function() {
    current += 1
  }
  var isDone = function() {
    return current >= obj.length
  }
  var getCurrItem = function() {
    return obj[current]
  }
  return {
    next: next,
    isDone: isDone,
    getCurrItem: getCurrItem,
  }
}

var compare = function(iterator1, iterator2) {
  while (!iterator1.isDone() && !iterator2.isDone()) {
    if (iterator1.getCurrItem() !== iterator2.getCurrItem()) {
      throw new Error('iterator1 和iterator2 不相等')
    }
    iterator1.next()
    iterator2.next()
  }
  alert('iterator1 和iterator2 相等')
}
var iterator1 = Iterator([1, 2, 3])
var iterator2 = Iterator([1, 2, 3])
compare(iterator1, iterator2)

五 发布-订阅模式(观察者模式)

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

  • 推模型、拉模型
  • 异步编程、替代回调函数
  • 不用再显示的调用另一个对象的接口

全局发布-订阅对象

js
var Event = (function() {
  var clientList = {},
    listen,
    trigger,
    remove
  listen = function(key, fn) {
    if (!clientList[key]) {
      clientList[key] = []
    }
    clientList[key].push(fn)
  }
  trigger = function() {
    var key = Array.prototype.shift.call(arguments),
      fns = clientList[key]
    if (!fns || fns.length === 0) {
      return false
    }
    for (var i = 0, fn; (fn = fns[i++]); ) {
      fn.apply(this, arguments)
    }
  }
  remove = function(key, fn) {
    var fns = clientList[key]
    if (!fns) {
      return false
    }
    if (!fn) {
      fns && (fns.length = 0)
    } else {
      for (var l = fns.length - 1; l >= 0; l--) {
        var _fn = fns[l]
        if (_fn === fn) {
          fns.splice(l, 1)
        }
      }
    }
  }
  return {
    listen: listen,
    trigger: trigger,
    remove: remove,
  }
})()

Event.listen('squareMeter88', function(price) {
  // 小红订阅消息
  console.log('价格= ' + price) // 输出:'价格=2000000'
})

离线事件与命名空间

js
var Event = (function() {
  var global = this,
    Event,
    _default = 'default'
  Event = (function() {
    var _listen,
      _trigger,
      _remove,
      _slice = Array.prototype.slice,
      _shift = Array.prototype.shift,
      _unshift = Array.prototype.unshift,
      namespaceCache = {},
      _create,
      find,
      each = function(ary, fn) {
        var ret
        for (var i = 0, l = ary.length; i < l; i++) {
          var n = ary[i]
          ret = fn.call(n, i, n)
        }
        return ret
      }
    _listen = function(key, fn, cache) {
      if (!cache[key]) {
        cache[key] = []
      }
      cache[key].push(fn)
    }
    _remove = function(key, cache, fn) {
      if (cache[key]) {
        if (fn) {
          for (var i = cache[key].length; i >= 0; i--) {
            if (cache[key] === fn) {
              cache[key].splice(i, 1)
            }
          }
        } else {
          cache[key] = []
        }
      }
    }
    _trigger = function() {
      var cache = _shift.call(arguments),
        key = _shift.call(arguments),
        args = arguments,
        _self = this,
        ret,
        stack = cache[key]
      if (!stack || !stack.length) {
        return
      }
      return each(stack, function() {
        return this.apply(_self, args)
      })
    }
    _create = function(namespace) {
      var namespace = namespace || _default
      var cache = {},
        offlineStack = [], // 离线事件
        ret = {
          listen: function(key, fn, last) {
            _listen(key, fn, cache)
            if (offlineStack === null) {
              return
            }
            if (last === 'last') {
            } else {
              each(offlineStack, function() {
                this()
              })
            }
            offlineStack = null
          },
          one: function(key, fn, last) {
            _remove(key, cache)
            this.listen(key, fn, last)
          },
          remove: function(key, fn) {
            _remove(key, cache, fn)
          },
          trigger: function() {
            var fn,
              args,
              _self = this
            _unshift.call(arguments, cache)
            args = arguments
            fn = function() {
              return _trigger.apply(_self, args)
            }
            if (offlineStack) {
              return offlineStack.push(fn)
            }
            return fn()
          },
        }
      return namespace
        ? namespaceCache[namespace]
          ? namespaceCache[namespace]
          : (namespaceCache[namespace] = ret)
        : ret
    }
    return {
      create: _create,
      one: function(key, fn, last) {
        var event = this.create()
        event.one(key, fn, last)
      },
      remove: function(key, fn) {
        var event = this.create()
        event.remove(key, fn)
      },
      listen: function(key, fn, last) {
        var event = this.create()
        event.listen(key, fn, last)
      },
      trigger: function() {
        var event = this.create()
        event.trigger.apply(this, arguments)
      },
    }
  })()
  return Event
})()

六 命令模式

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系

  • 回调函数的一个面向对象的替代品
js
var RefreshMenuBarCommand = function(receiver) {
  return {
    execute: function() {
      receiver.refresh()
    },
  }
}
var setCommand = function(button, command) {
  button.onclick = function() {
    command.execute()
  }
}
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
setCommand(button1, refreshMenuBarCommand)

宏命令、智能命令

js
var closeDoorCommand = {
  execute: function() {
    console.log('关门')
  },
}
var openPcCommand = {
  execute: function() {
    console.log('开电脑')
  },
}

var openQQCommand = {
  execute: function() {
    console.log('登录QQ')
  },
}

var MacroCommand = function() {
  return {
    commandsList: [],
    add: function(command) {
      this.commandsList.push(command)
    },
    execute: function() {
      for (var i = 0, command; (command = this.commandsList[i++]); ) {
        command.execute()
      }
    },
  }
}
var macroCommand = MacroCommand()
macroCommand.add(closeDoorCommand)
macroCommand.add(openPcCommand)
macroCommand.add(openQQCommand)
macroCommand.execute()

七 组合模式

组合模式将对象组合成树形结构,以表示“部分-整体”的层次机构

  • 表示树形结构
  • 利用对象多态性统一对待组合对象和单个对象
  • 组合模式不是父子关系
  • 对叶对象操作的一致性
  • 双向映射关系
  • 用职责链模式提高组合模式性能
js
var Folder = function(name) {
  this.name = name
  this.parent = null //增加this.parent 属性
  this.files = []
}

Folder.prototype.add = function(file) {
  file.parent = this //设置父对象
  this.files.push(file)
}

Folder.prototype.scan = function() {
  console.log('开始扫描文件夹: ' + this.name)
  for (var i = 0, file, files = this.files; (file = files[i++]); ) {
    file.scan()
  }
}

Folder.prototype.remove = function() {
  if (!this.parent) {
    //根节点或者树外的游离节点
    return
  }
  for (var files = this.parent.files, l = files.length - 1; l >= 0; l--) {
    var file = files[l]
    if (file === this) {
      files.splice(l, 1)
    }
  }
}

var File = function(name) {
  this.name = name
  this.parent = null
}

File.prototype.add = function() {
  throw new Error('不能添加在文件下面')
}

File.prototype.scan = function() {
  console.log('开始扫描文件: ' + this.name)
}

File.prototype.remove = function() {
  if (!this.parent) {
    //根节点或者树外的游离节点
    return
  }

  for (var files = this.parent.files, l = files.length - 1; l >= 0; l--) {
    var file = files[l]
    if (file === this) {
      files.splice(l, 1)
    }
  }
}

var folder = new Folder('学习资料')
var folder1 = new Folder('JavaScript')
var file1 = new Folder('深入浅出Node.js')

folder1.add(new File('JavaScript 设计模式与开发实践'))
folder.add(folder1)
folder.add(file1)
folder1.remove() //移除文件夹
folder.scan()

八 模板方法模式

  • 抽象父类
  • 具体的实现子类
js
var Beverage = function() {}
Beverage.prototype.boilWater = function() {
  console.log('把水煮沸')
}

Beverage.prototype.brew = function() {} // 空方法,应该由子类重写
Beverage.prototype.pourInCup = function() {} // 空方法,应该由子类重写
Beverage.prototype.addCondiments = function() {} // 空方法,应该由子类重写
Beverage.prototype.init = function() {
  this.boilWater()
  this.brew()
  this.pourInCup()
  this.addCondiments()
}

var Coffee = function() {}
Coffee.prototype = new Beverage()

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

var Tea = function() {}
Tea.prototype = new Beverage()

Tea.prototype.brew = function() {
  console.log('用沸水浸泡茶叶')
}
Tea.prototype.pourInCup = function() {
  console.log('把茶倒进杯子')
}
Tea.prototype.addCondiments = function() {
  console.log('加柠檬')
}
var tea = new Tea()
tea.init()

钩子方法

js
var 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()
  if (this.customerWantsCondiments()) {
    // 如果挂钩返回true,则需要调料
    this.addCondiments()
  }
}
var CoffeeWithHook = function() {}
CoffeeWithHook.prototype = new Beverage()
CoffeeWithHook.prototype.brew = function() {
  console.log('用沸水冲泡咖啡')
}
CoffeeWithHook.prototype.pourInCup = function() {
  console.log('把咖啡倒进杯子')
}
CoffeeWithHook.prototype.addCondiments = function() {
  console.log('加糖和牛奶')
}
CoffeeWithHook.prototype.customerWantsCondiments = function() {
  return window.confirm('请问需要调料吗?')
}
var coffeeWithHook = new CoffeeWithHook()

好莱坞原则

不要来找我,我会给你打电话

真的需要继承吗?

js
var Beverage = function(param) {
  var boilWater = function() {
    console.log('把水煮沸')
  }
  var brew =
    param.brew ||
    function() {
      throw new Error('必须传递brew 方法')
    }
  var pourInCup =
    param.pourInCup ||
    function() {
      throw new Error('必须传递pourInCup 方法')
    }
  var addCondiments =
    param.addCondiments ||
    function() {
      throw new Error('必须传递addCondiments 方法')
    }
  var F = function() {}
  F.prototype.init = function() {
    boilWater()
    brew()
    pourInCup()
    addCondiments()
  }
  return F
}
var Coffee = Beverage({
  brew: function() {
    console.log('用沸水冲泡咖啡')
  },
  pourInCup: function() {
    console.log('把咖啡倒进杯子')
  },
  addCondiments: function() {
    console.log('加糖和牛奶')
  },
})

var Tea = Beverage({
  brew: function() {
    console.log('用沸水浸泡茶叶')
  },
  pourInCup: function() {
    console.log('把茶倒进杯子')
  },
  addCondiments: function() {
    console.log('加柠檬')
  },
})
var coffee = new Coffee()
coffee.init()
var tea = new Tea()
tea.init()

九 享元模式

  • 用于性能优化的模式
  • 运用共享技术来有效支持大量细粒度的对象
  • 减少共享对象的数量

创建了 100 个对象

js
var Model = function(sex, underwear) {
  this.sex = sex
  this.underwear = underwear
}
Model.prototype.takePhoto = function() {
  console.log('sex= ' + this.sex + ' underwear=' + this.underwear)
}
for (var i = 1; i <= 50; i++) {
  var maleModel = new Model('male', 'underwear' + i)
  maleModel.takePhoto()
}
for (var j = 1; j <= 50; j++) {
  var femaleModel = new Model('female', 'underwear' + j)
  femaleModel.takePhoto()
}

只创建了 2 个对象

js
var Model = function(sex) {
  this.sex = sex
}
Model.prototype.takePhoto = function() {
  console.log('sex= ' + this.sex + ' underwear=' + this.underwear)
}

var maleModel = new Model('male'),
  femaleModel = new Model('female')

for (var i = 1; i <= 50; i++) {
  maleModel.underwear = 'underwear' + i
  maleModel.takePhoto()
}

for (var j = 1; j <= 50; j++) {
  femaleModel.underwear = 'underwear' + j
  femaleModel.takePhoto()
}

内部状态和外部状态

  • 内部状态存储于对象内部
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会改变
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享

文件上传例子

js
//剥离外部状态
var Upload = function(uploadType) {
  this.uploadType = uploadType
}

Upload.prototype.delFile = function(id) {
  uploadManager.setExternalState(id, this) // (1)
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom)
  }

  if (window.confirm('确定要删除该文件吗? ' + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom)
  }
}

//工厂进行对象实例化
var UploadFactory = (function() {
  var createdFlyWeightObjs = {}
  return {
    create: function(uploadType) {
      if (createdFlyWeightObjs[uploadType]) {
        return createdFlyWeightObjs[uploadType]
      }
      return (createdFlyWeightObjs[uploadType] = new Upload(uploadType))
    },
  }
})()

//管理器封装外部状态
var uploadManager = (function() {
  var uploadDatabase = {}
  return {
    add: function(id, uploadType, fileName, fileSize) {
      var flyWeightObj = UploadFactory.create(uploadType)
      var 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) {
      var uploadData = uploadDatabase[id]
      for (var i in uploadData) {
        flyWeightObj[i] = uploadData[i]
      }
    },
  }
})()

var id = 0
window.startUpload = function(uploadType, files) {
  for (var i = 0, file; (file = files[i++]); ) {
    var 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,
  },
])

享元的适用性

  • 一个程序中使用了大量的相似对象
  • 由于使用了大量对象,造成很大的内存开销
  • 对象大多数状态都可以变为外部状态
  • 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象

没有内部状态的享元

js
var UploadFactory = (function() {
  var uploadObj = {}
  return {
    create: function() {
      if (uploadObj) {
        return uploadObj
      }
      return (uploadObj = new Upload())
    },
  }
})()

对象池

js
var toolTipFactory = (function() {
  var toolTipPool = [] // toolTip 对象池
  return {
    create: function() {
      if (toolTipPool.length === 0) {
        // 如果对象池为空
        var div = document.createElement('div') // 创建一个dom
        document.body.appendChild(div)
        return div
      } else {
        // 如果对象池里不为空
        return toolTipPool.shift() // 则从对象池中取出一个dom
      }
    },
    recover: function(tooltipDom) {
      return toolTipPool.push(tooltipDom) // 对象池回收dom
    },
  }
})()

var ary = []
for (var i = 0, str; (str = ['A', 'B'][i++]); ) {
  var toolTip = toolTipFactory.create()
  toolTip.innerHTML = str
  ary.push(toolTip)
}

for (var i = 0, toolTip; (toolTip = ary[i++]); ) {
  toolTipFactory.recover(toolTip)
}

for (var i = 0, str; (str = ['A', 'B', 'C', 'D', 'E', 'F'][i++]); ) {
  var toolTip = toolTipFactory.create()
  toolTip.innerHTML = str
}

通用对象池实现

js
var objectPoolFactory = function(createObjFn) {
  var objectPool = []
  return {
    create: function() {
      var obj =
        objectPool.length === 0
          ? createObjFn.apply(this, arguments)
          : objectPool.shift()
      return obj
    },
    recover: function(obj) {
      objectPool.push(obj)
    },
  }
}

var iframeFactory = objectPoolFactory(function() {
  var iframe = document.createElement('iframe')
  document.body.appendChild(iframe)
  iframe.onload = function() {
    iframe.onload = null // 防止iframe 重复加载的bug
    iframeFactory.recover(iframe) // iframe 加载完成之后回收节点
  }
  return iframe
})

var iframe1 = iframeFactory.create()
iframe1.src = 'http:// baidu.com'
var iframe2 = iframeFactory.create()
iframe2.src = 'http:// QQ.com'
setTimeout(function() {
  var iframe3 = iframeFactory.create()
  iframe3.src = 'http:// 163.com'
}, 3000)

十 职责连模式

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

js
var order500 = function(orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log('500 元定金预购,得到100 优惠券')
  } else {
    return 'nextSuccessor' // 我不知道下一个节点是谁,反正把请求往后面传递
  }
}
var order200 = function(orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log('200 元定金预购,得到50 优惠券')
  } else {
    return 'nextSuccessor' // 我不知道下一个节点是谁,反正把请求往后面传递
  }
}
var orderNormal = function(orderType, pay, stock) {
  if (stock > 0) {
    console.log('普通购买,无优惠券')
  } else {
    console.log('手机库存不足')
  }
}
// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
var Chain = function(fn) {
  this.fn = fn
  this.successor = null
}
Chain.prototype.setNextSuccessor = function(successor) {
  return (this.successor = successor)
}
Chain.prototype.passRequest = function() {
  var ret = this.fn.apply(this, arguments)
  if (ret === 'nextSuccessor') {
    return (
      this.successor &&
      this.successor.passRequest.apply(this.successor, arguments)
    )
  }
  return ret
}
var chainOrder500 = new Chain(order500)
var chainOrder200 = new Chain(order200)
var chainOrderNormal = new Chain(orderNormal)
chainOrder500.setNextSuccessor(chainOrder200)
chainOrder200.setNextSuccessor(chainOrderNormal)
chainOrder500.passRequest(1, true, 500) // 输出:500 元定金预购,得到100 惠券
chainOrder500.passRequest(2, true, 500) // 输出:200 元定金预购,得到50 惠券
chainOrder500.passRequest(3, true, 500) // 输出:普通购买,无优惠券
chainOrder500.passRequest(1, false, 0) // 输出:手机库存不足

异步的职责连

js
Chain.prototype.next = function() {
  return (
    this.successor &&
    this.successor.passRequest.apply(this.successor, arguments)
  )
}

var fn1 = new Chain(function() {
  console.log(1)
  return 'nextSuccessor'
})

var fn2 = new Chain(function() {
  console.log(2)
  var self = this
  setTimeout(function() {
    self.next()
  }, 1000)
})

var fn3 = new Chain(function() {
  console.log(3)
})

fn1.setNextSuccessor(fn2).setNextSuccessor(fn3)
fn1.passRequest()

职责链的优缺点

  • 不能保证每个请求一定会被链中的节点处理
  •  职责链过长带来的性能损耗

AOP 实现职责链

js
Function.prototype.after = function(fn) {
  var self = this
  return function() {
    var ret = self.apply(this, arguments)
    if (ret === 'nextSuccessor') {
      return fn.apply(this, arguments)
    }
    return ret
  }
}

var order = order500yuan.after(order200yuan).after(orderNormal)
order(1, true, 500) // 输出:500 元定金预购,得到100 优惠券
order(2, true, 500) // 输出:200 元定金预购,得到50 优惠券
order(1, false, 500) // 输出:普通购买,无优惠券

十一 中介者模式

  • 解除对象与对象之间的耦合关系
  • 迎合迪米特法则的实现

泡泡堂游戏

js
function Player(name, teamColor) {
  this.name = name // 角色名字
  this.teamColor = teamColor // 队伍颜色
  this.state = 'alive' // 玩家生存状态
}

Player.prototype.win = function() {
  console.log(this.name + ' won ')
}

Player.prototype.lose = function() {
  console.log(this.name + ' lost')
}
/*******************玩家死亡*****************/
Player.prototype.die = function() {
  this.state = 'dead'
  playerDirector.reciveMessage('playerDead', this) // 给中介者发送消息,玩家死亡
}
/*******************移除玩家*****************/
Player.prototype.remove = function() {
  playerDirector.reciveMessage('removePlayer', this) // 给中介者发送消息,移除一个玩家
}

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

var playerDirector = (function() {
  var players = {}, // 保存所有玩家
    operations = {} // 中介者可以执行的操作
  /****************新增一个玩家***************************/
  operations.addPlayer = function(player) {
    var teamColor = player.teamColor // 玩家的队伍颜色
    players[teamColor] = players[teamColor] || [] // 如果该颜色的玩家还没有成立队伍,则

    players[teamColor].push(player) // 添加玩家进队伍
  }
  /****************移除一个玩家***************************/
  operations.removePlayer = function(player) {
    var teamColor = player.teamColor, // 玩家的队伍颜色
      teamPlayers = players[teamColor] || [] // 该队伍所有成员
    for (var 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) {
    // 玩家死亡
    var teamColor = player.teamColor,
      teamPlayers = players[teamColor] // 玩家所在队伍
    var all_dead = true
    for (var i = 0, player; (player = teamPlayers[i++]); ) {
      if (player.state !== 'dead') {
        all_dead = false
        break
      }
    }
    if (all_dead === true) {
      // 全部死亡
      for (var i = 0, player; (player = teamPlayers[i++]); ) {
        player.lose() // 本队所有玩家lose
      }
      for (var color in players) {
        if (color !== teamColor) {
          var teamPlayers = players[color] // 其他队伍的玩家
          for (var i = 0, player; (player = teamPlayers[i++]); ) {
            player.win() // 其他队伍所有玩家win
          }
        }
      }
    }
  }

  var reciveMessage = function() {
    var message = Array.prototype.shift.call(arguments) // arguments 的第一个参数为消息名称
    operations[message].apply(this, arguments)
  }

  return {
    reciveMessage: reciveMessage,
  }
})()

// 红队:
var player1 = playerFactory('皮蛋', 'red'),
  player2 = playerFactory('小乖', 'red'),
  player3 = playerFactory('宝宝', 'red'),
  player4 = playerFactory('小强', 'red')
// 蓝队:
var player5 = playerFactory('黑妞', 'blue'),
  player6 = playerFactory('葱头', 'blue'),
  player7 = playerFactory('胖墩', 'blue'),
  player8 = playerFactory('海盗', 'blue')
player1.die()
player2.die()
player3.die()
player4.die()

player1.remove()
player2.remove()
player3.die()
player4.die()

player1.changeTeam('blue')
player2.die()
player3.die()
player4.die()

购买商品

js
var goods = {
  // 手机库存
  'red|32G': 3,
  'red|16G': 0,
  'blue|32G': 1,
  'blue|16G': 6,
}
var mediator = (function() {
  var colorSelect = document.getElementById('colorSelect'),
    memorySelect = document.getElementById('memorySelect'),
    numberInput = document.getElementById('numberInput'),
    colorInfo = document.getElementById('colorInfo'),
    memoryInfo = document.getElementById('memoryInfo'),
    numberInfo = document.getElementById('numberInfo'),
    nextBtn = document.getElementById('nextBtn')
  return {
    changed: function(obj) {
      var color = colorSelect.value, // 颜色
        memory = memorySelect.value, // 内存
        number = numberInput.value, // 数量
        stock = goods[color + '|' + memory] // 颜色和内存对应的手机库存数量

      if (obj === colorSelect) {
        // 如果改变的是选择颜色下拉框
        colorInfo.innerHTML = color
      } else if (obj === memorySelect) {
        memoryInfo.innerHTML = memory
      } else if (obj === numberInput) {
        numberInfo.innerHTML = number
      }
      if (!color) {
        nextBtn.disabled = true
        nextBtn.innerHTML = '请选择手机颜色'
        return
      }
      if (!memory) {
        nextBtn.disabled = true
        nextBtn.innerHTML = '请选择内存大小'
        return
      }
      if (((number - 0) | 0) !== number - 0) {
        // 输入购买数量是否为正整数
        nextBtn.disabled = true
        nextBtn.innerHTML = '请输入正确的购买数量'
        return
      }
      nextBtn.disabled = false
      nextBtn.innerHTML = '放入购物车'
    },
  }
})()

// 事件函数:
colorSelect.onchange = function() {
  mediator.changed(this)
}
memorySelect.onchange = function() {
  mediator.changed(this)
}
numberInput.oninput = function() {
  mediator.changed(this)
}

十二 装饰者模式

动态的给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象

模拟传统面向对象语言

js
var Plane = function() {}

Plane.prototype.fire = function() {
  console.log('发射普通子弹')
}

var MissileDecorator = function(plane) {
  this.plane = plane
}
MissileDecorator.prototype.fire = function() {
  this.plane.fire()
  console.log('发射导弹')
}
var AtomDecorator = function(plane) {
  this.plane = plane
}
AtomDecorator.prototype.fire = function() {
  this.plane.fire()
  console.log('发射原子弹')
}

var plane = new Plane()
plane = new MissileDecorator(plane)
plane = new AtomDecorator(plane)
plane.fire()

javascript 的装饰器

js
var plane = {
  fire: function() {
    console.log('发射普通子弹')
  },
}
var missileDecorator = function() {
  console.log('发射导弹')
}
var atomDecorator = function() {
  console.log('发射原子弹')
}
var fire1 = plane.fire
plane.fire = function() {
  fire1()
  missileDecorator()
}
var fire2 = plane.fire
plane.fire = function() {
  fire2()
  atomDecorator()
}
plane.fire()
// 分别输出: 发射普通子弹、发射导弹、发射原子弹

用 AOP 装饰函数

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

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

AOP 的应用实例

数据统计上报

js
Function.prototype.after = function(afterfn) {
  var __self = this
  return function() {
    var ret = __self.apply(this, arguments)
    afterfn.apply(this, arguments)
    return ret
  }
}
var showLogin = function() {
  console.log('打开登录浮层')
}
var log = function() {
  console.log('上报标签为: ' + this.getAttribute('tag'))
}

showLogin = showLogin.after(log) // 打开登录浮层之后上报数据
document.getElementById('button').onclick = showLogin

用 AOP 动态改变函数的参数

js
var func = function(param) {
  console.log(param)
}

func = func.before(function(param) {
  param.b = 'b'
})

func({ a: 'a' })

插件式的表单验证

js
Function.prototype.before = function(beforefn) {
  var __self = this
  return function() {
    if (beforefn.apply(this, arguments) === false) {
      // beforefn 返回false 的情况直接return,不再执行后面的原函数
      return
    }
    return __self.apply(this, arguments)
  }
}

var validata = function() {
  if (username.value === '') {
    alert('用户名不能为空')
    return false
  }
  if (password.value === '') {
    alert('密码不能为空')
    return false
  }
}
var formSubmit = function() {
  var param = {
    username: username.value,
    password: password.value,
  }
  ajax('http:// xxx.com/login', param)
}

formSubmit = formSubmit.before(validata)

submitBtn.onclick = function() {
  formSubmit()
}

十三 状态模式

区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变

电灯程序

js
var OffLightState = function(light) {
  this.light = light
}

OffLightState.prototype.buttonWasPressed = function() {
  console.log('弱光') // offLightState 对应的行为
  this.light.setState(this.light.weakLightState) // 切换状态到weakLightState
}
// WeakLightState:
var WeakLightState = function(light) {
  this.light = light
}

WeakLightState.prototype.buttonWasPressed = function() {
  console.log('强光') // weakLightState 对应的行为
  this.light.setState(this.light.strongLightState) // 切换状态到strongLightState
}
// StrongLightState:
var StrongLightState = function(light) {
  this.light = light
}

StrongLightState.prototype.buttonWasPressed = function() {
  console.log('关灯') // strongLightState 对应的行为
  this.light.setState(this.light.offLightState) // 切换状态到offLightState
}

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

Light.prototype.init = function() {
  var button = document.createElement('button'),
    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
}

var light = new Light()
light.init()

增加抽象类的变通

js
var State = function() {}
State.prototype.buttonWasPressed = function() {
  throw new Error('父类的buttonWasPressed 方法必须被重写')
}
var SuperStrongLightState = function(light) {
  this.light = light
}

SuperStrongLightState.prototype = new State() // 继承抽象父类
SuperStrongLightState.prototype.buttonWasPressed = function() {
  // 重写buttonWasPressed 方法
  console.log('关灯')
  this.light.setState(this.light.offLightState)
}

文件上传

js
window.external.upload = function(state) {
  console.log(state) // 可能为sign、uploading、done、error
}
var plugin = (function() {
  var plugin = document.createElement('embed')
  plugin.style.display = 'none'
  plugin.type = 'application/txftn-webkit'
  plugin.sign = function() {
    console.log('开始文件扫描')
  }
  plugin.pause = function() {
    console.log('暂停文件上传')
  }
  plugin.uploading = function() {
    console.log('开始文件上传')
  }
  plugin.del = function() {
    console.log('删除文件上传')
  }
  plugin.done = function() {
    console.log('文件上传完成')
  }
  document.body.appendChild(plugin)
  return plugin
})()
var Upload = function(fileName) {
  this.plugin = plugin
  this.fileName = fileName
  this.button1 = null
  this.button2 = null
  this.signState = new SignState(this) // 设置初始状态为waiting
  this.uploadingState = new UploadingState(this)
  this.pauseState = new PauseState(this)
  this.doneState = new DoneState(this)
  this.errorState = new ErrorState(this)
  this.currState = this.signState // 设置当前状态
}
Upload.prototype.init = function() {
  var that = this
  this.dom = document.createElement('div')
  this.dom.innerHTML =
    '<span>文件名称:' +
    this.fileName +
    '</span>\
<button data-action="button1">扫描中</button>\
<button data-action="button2">删除</button>'
  document.body.appendChild(this.dom)
  this.button1 = this.dom.querySelector('[data-action="button1"]')
  this.button2 = this.dom.querySelector('[data-action="button2"]')
  this.bindEvent()
}
Upload.prototype.bindEvent = function() {
  var self = this
  this.button1.onclick = function() {
    self.currState.clickHandler1()
  }
  this.button2.onclick = function() {
    self.currState.clickHandler2()
  }
}
Upload.prototype.sign = function() {
  this.plugin.sign()
  this.currState = this.signState
}
Upload.prototype.uploading = function() {
  this.button1.innerHTML = '正在上传,点击暂停'
  this.plugin.uploading()
  this.currState = this.uploadingState
}
Upload.prototype.pause = function() {
  this.button1.innerHTML = '已暂停,点击继续上传'
  this.plugin.pause()
  this.currState = this.pauseState
}
Upload.prototype.done = function() {
  this.button1.innerHTML = '上传完成'
  this.plugin.done()
  this.currState = this.doneState
}
Upload.prototype.error = function() {
  this.button1.innerHTML = '上传失败'
  this.currState = this.errorState
}
Upload.prototype.del = function() {
  this.plugin.del()
  this.dom.parentNode.removeChild(this.dom)
}
var StateFactory = (function() {
  var State = function() {}
  State.prototype.clickHandler1 = function() {
    throw new Error('子类必须重写父类的clickHandler1 方法')
  }
  State.prototype.clickHandler2 = function() {
    throw new Error('子类必须重写父类的clickHandler2 方法')
  }
  return function(param) {
    var F = function(uploadObj) {
      this.uploadObj = uploadObj
    }
    F.prototype = new State()
    for (var i in param) {
      F.prototype[i] = param[i]
    }
    return F
  }
})()
var SignState = StateFactory({
  clickHandler1: function() {
    console.log('扫描中,点击无效...')
  },
  clickHandler2: function() {
    console.log('文件正在上传中,不能删除')
  },
})
var UploadingState = StateFactory({
  clickHandler1: function() {
    this.uploadObj.pause()
  },
  clickHandler2: function() {
    console.log('文件正在上传中,不能删除')
  },
})
var PauseState = StateFactory({
  clickHandler1: function() {
    this.uploadObj.uploading()
  },
  clickHandler2: function() {
    this.uploadObj.del()
  },
})
var DoneState = StateFactory({
  clickHandler1: function() {
    console.log('文件已完成上传, 点击无效')
  },
  clickHandler2: function() {
    this.uploadObj.del()
  },
})
var ErrorState = StateFactory({
  clickHandler1: function() {
    console.log('文件上传失败, 点击无效')
  },
  clickHandler2: function() {
    this.uploadObj.del()
  },
})
var uploadObj = new Upload('JavaScript 设计模式与开发实践')
uploadObj.init()
window.external.upload = function(state) {
  uploadObj[state]()
}
window.external.upload('sign')
setTimeout(function() {
  window.external.upload('uploading') // 1 秒后开始上传
}, 1000)
setTimeout(function() {
  window.external.upload('done') // 5 秒后上传完成
}, 5000)

javascript 版本的状态机

第一种

js
var Light = function() {
  this.currState = FSM.off // 设置当前状态
  this.button = null
}

Light.prototype.init = function() {
  var button = document.createElement('button'),
    self = this
  button.innerHTML = '已关灯'
  this.button = document.body.appendChild(button)
  this.button.onclick = function() {
    self.currState.buttonWasPressed.call(self) // 把请求委托给FSM 状态机
  }
}
var FSM = {
  off: {
    buttonWasPressed: function() {
      console.log('关灯')
      this.button.innerHTML = '下一次按我是开灯'
      this.currState = FSM.on
    },
  },
  on: {
    buttonWasPressed: function() {
      console.log('开灯')
      this.button.innerHTML = '下一次按我是关灯'
      this.currState = FSM.off
    },
  },
}
var light = new Light()
light.init()

第二种

js
var delegate = function(client, delegation) {
  return {
    buttonWasPressed: function() {
      // 将客户的操作委托给delegation 对象
      return delegation.buttonWasPressed.apply(client, arguments)
    },
  }
}

var FSM = {
  off: {
    buttonWasPressed: function() {
      console.log('关灯')
      this.button.innerHTML = '下一次按我是开灯'
      this.currState = this.onState
    },
  },
  on: {
    buttonWasPressed: function() {
      console.log('开灯')
      this.button.innerHTML = '下一次按我是关灯'
      this.currState = this.offState
    },
  },
}

var Light = function() {
  this.offState = delegate(this, FSM.off)
  this.onState = delegate(this, FSM.on)
  this.currState = this.offState // 设置初始状态为关闭状态
  this.button = null
}

Light.prototype.init = function() {
  var button = document.createElement('button'),
    self = this
  button.innerHTML = '已关灯'
  this.button = document.body.appendChild(button)
  this.button.onclick = function() {
    self.currState.buttonWasPressed()
  }
}
var light = new Light()
light.init()

表驱动的有限状态机

下一个状态是由当前状态和行为共同决定的

js
var fsm = StateMachine.create({
  initial: 'off',
  events: [
    {
      name: 'buttonWasPressed',
      from: 'off',
      to: 'on',
    },
    {
      name: 'buttonWasPressed',
      from: 'on',
      to: 'off',
    },
  ],
  callbacks: {
    onbuttonWasPressed: function(event, from, to) {
      console.log(arguments)
    },
  },
  error: function(eventName, from, to, args, errorCode, errorMessage) {
    console.log(arguments) // 从一种状态试图切换到一种不可能到达的状态的时候
  },
})

button.onclick = function() {
  fsm.buttonWasPressed()
}

参考

javascript-state-machine

十四 适配器模式

解决两个软件实体间的接口不兼容的问题

例一

js
var googleMap = {
  show: function() {
    console.log('开始渲染谷歌地图')
  },
}
var baiduMap = {
  display: function() {
    console.log('开始渲染百度地图')
  },
}
var baiduMapAdapter = {
  show: function() {
    return baiduMap.display()
  },
}

renderMap(googleMap) // 输出:开始渲染谷歌地图
renderMap(baiduMapAdapter) // 输出:开始渲染百度地图

例二

js
var guangdongCity = {
  shenzhen: 11,
  guangzhou: 12,
  zhuhai: 13,
}
var getGuangdongCity = function() {
  var guangdongCity = [
    {
      name: 'shenzhen',
      id: 11,
    },
    {
      name: 'guangzhou',
      id: 12,
    },
  ]
  return guangdongCity
}
var render = function(fn) {
  console.log('开始渲染广东省地图')
  document.write(JSON.stringify(fn()))
}
var addressAdapter = function(oldAddressfn) {
  var address = {},
    oldAddress = oldAddressfn()
  for (var i = 0, c; (c = oldAddress[i++]); ) {
    address[c.name] = c.id
  }
  return function() {
    return address
  }
}
render(addressAdapter(getGuangdongCity))