最近在研读了腾讯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 resultreturn function () {return result || (result = fn.apply(this, arguments))}}// 创建div浮窗let createSingleLoginLayer = getSingle(createLoginLayer)// 点击多次都只会创建一个新的登录浮层divdocument.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}}})()// 代理对象proxyImagelet proxyImage = (function () {let img = new Imageimg.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 = 1for (let i = 0, l = arguments.length; i < l; i++) {a *= arguments[i]}return a
}
// 加和
let plus = function () {console.log('开始计算加和')let a = 1for (let i = 0, l = arguments.length; i < l; i++) {a += arguments[i]}return a
}
// 减法
let subtraction = function () {console.log('开始计算减法')let a = 1for (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 = namethis.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 : falselet 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 = 0window.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 = thisreturn 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 = newTeamColoroperations.addPlayer(player)}/******************* 玩家死亡 ******************/operations.playerDead = function (player) {// 玩家的队伍颜色let teamColor = player.teamColor// 玩家所在队伍let teamPlayers = players[teamColor]let all_dead = truefor (let i = 0, player; player = teamPlayers[i++];) {if (player.state !== 'dead') {all_dead = falsebreak}}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 = thisreturn 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方法必须被重写')}// OffLightStatelet OffLightState = function (light) {this.light = light}OffLightState.prototype = new State()OffLightState.prototype.buttonWasPressed = function () {console.log('弱光')// 切换状态到weakLightStatethis.light.setState(this.light.weakLightState)}// WeakLightStatelet WeakLightState = function (light) {this.light = light}WeakLightState.prototype = new State()WeakLightState.prototype.buttonWasPressed = function () {console.log('强光')this.light.setState(this.light.strongLightState)}// StrongLightStatelet 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 = thisthis.button = document.body.appendChild(button)this.button.innerHTML = '开关'this.currState = this.offLightStatethis.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)

JavaScript设计模式与开发实践相关推荐

  1. 《JavaScript设计模式与开发实践》模式篇(12)—— 装饰者模式

    在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活, 还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之 改变;另一方面,继承这种功能复 ...

  2. JavaScript设计模式与开发实践——JavaScript的多态

    "多态"一词源于希腊文polymorphism,拆开来看是poly(复数)+ morph(形态)+ ism,从字面上我们可以理解为复数形态. 多态的实际含义是:同一操作作用于不同的 ...

  3. 《JavaScript设计模式与开发实践》阅读摘要

    <JavaScript设计模式与开发实践>作者:曾探 系统的介绍了各种模式,以及js中的实现.应用,以及超大量高质量代码,绝对值得一读 面向对象的js 静态类型:编译时便已确定变量的类型 ...

  4. JS代理模式《JavaScript设计模式与开发实践》阅读笔记

    代理模式 代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问. 保护代理和虚拟代理 保护代理:当有许多需求要向某对象发出一些请求时,可以设置保护代理,通过一些条件判断对请求进行过滤. 虚拟 ...

  5. 专访《Javascript设计模式与开发实践》作者曾探:爱编程 爱生活

     专访<Javascript设计模式与开发实践>作者曾探:爱编程 爱生活 发表于12小时前| 2742次阅读| 来源CSDN| 8 条评论| 作者夏梦竹 专访曾探图书作者Javascr ...

  6. 《JavaScript设计模式与开发实践》模式篇(5)—— 观察者模式

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

  7. 《JavaScript设计模式与开发实践》模式篇(3)—— 代理模式

    代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问 故事背景: 假设当 A 在心情好的时候收到花,小明表白成功的几率有 60%,而当 A 在心情差的时候收到花,小明表白的成功率无限趋近于 ...

  8. JavaScript设计模式与开发实践 | 02 - this、call和apply

    this JavaScript的this总是指向一个对象,至于指向哪个对象,是在运行时基于函数的执行环境的动态绑定的,而非函数被声明时的环境. this的指向 this的指向大致可以分为以下4类: 作 ...

  9. 《JavaScript设计模式与开发实践》原则篇(3)—— 开放-封闭原则

    在面向对象的程序设计中,开放封闭原则(OCP)是最重要的一条原则.很多时候,一个程序具有良好的设计,往往说明它是符合开放封闭原则的. 当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增 ...

  10. JavaScript设计模式与开发实践系列之单例模式

    本系列为<JavaScript设计模式与开发实践>(作者:曾探)学习总结,如想深入了解,请支持作者原版 单例模式 实现单例模式 单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的 ...

最新文章

  1. ArcEngine中的缩放地图
  2. AWS Lambda将数据保存在DynamoDB中
  3. Mac下,如何把项目托管到Github上(Github Desktop的使用)
  4. 论文浅尝 - ICLR2020 | 知道什么、如何以及为什么:基于方面的情感分析的近乎完整的解决方案...
  5. 用于快速排查Java的CPU性能问题(top us值过高)
  6. java中static关键字的理解(转载)
  7. MySQL性能半同步复制VS异步复制
  8. TortoiseSVN 执行清理( cleanUp )失败的解决方案
  9. javatodo框架中怎么配置路由
  10. 如何在Adobe Illustrator中绘制花园里的小矮人
  11. codeforces 贪心 Traps
  12. Vbs脚本编程简明教程之五
  13. 创建对象和实现原型继承的几种方式
  14. 锚具ovm是什么意思_OVM锚具
  15. Mysql 创建数据库\添加用户\用户授权
  16. 云计算与分布式技术-常见云的比较
  17. 专访丨兼容国内外市场的代码分析软件,鉴释科技帮助企业减少bug发生率
  18. java 将Object类型转换为long
  19. cadence allegro 17.2中的正负片
  20. 陷入多事之秋,阿里的价值观出问题了?

热门文章

  1. centos7基于k8s安装部署prometheus(普罗米修斯)
  2. 用java做出长方体的表面积_计算长方体、四棱锥的表面积和体积(Java)acm.sdut...
  3. Shiro高版本默认密钥的漏洞利用
  4. WebStorm2016.1.1免注册破解方法
  5. 「经济/商学/理财」简说
  6. C语言学习笔记《带你学C带你飞》P41-P61
  7. 李沐动手学深度学习V2-BERT微调和代码实现
  8. matlab 批量导入excel,matlab批量导入excel表格数据-Matlab如何导入excel数据
  9. 云锁和悬镜服务器哪个好,安全狗、悬镜、云锁、云帮手建议用哪个比较好?
  10. java中的terminated_解决maven build 无反应,直接terminated的问题