前阵子看了《JavaScript设计模式与开发实践》,非常不错的一本书,整理一些最常用的设计模式,以后再补充一些其它的。
单例模式
保证一个类仅有一个实例,并且提供一个访问它的全局访问点。
实现单例模式
利用构造函数的实例属性(不透明)
let CreateDiv = function (name) { this.name = name this.instance = null } CreateDiv.prototype.init = function () { let div = document.createElement( 'div' ) // .... } CreateDiv.creatInstance = function (name) { if (!this.instance) { this.instance = new CreateDiv(name) } return this.instance } let lee1 = CreateDiv.creatInstance('lee1') let lee2 = CreateDiv.creatInstance('lee2') lee1 === lee2 // true
利用闭包 (不透明)
let CreateDiv = function (name) { this.name = name } CreateDiv.prototype.init = function () { let div = document.createElement( 'div' ) // .... } CreateDiv.creatInstance = (function () { // 立即执行函数 let instance = null return function (name) { if (!instance) { instance = new CreateDiv(name) } return instance } })() let lee1 = CreateDiv.creatInstance('lee1') let lee2 = CreateDiv.creatInstance('lee2') lee1 === lee2 // true
单例类(透明)
上面两种实现方式不透明,需要研究代码的实现才知道调creatInstance方法,这样不太好啊,我门用普通类来实现
let Singleton = (function () { let instance = null let CreateDiv = function (name) { if (instance) return instance this.name = name this.init() // 创建一个div return instance = this // this指向新创建的实例对象 } CreateDiv.prototype.init = function () { let div = document.createElement( 'div' ) // .... } return CreateDiv })() let lee1 = new Singleton('lee1') let lee2 = new Singleton('lee2') lee1 === lee2 // true
代理类(代理模式 - 透明)
// 有一我们要创建100个div怎么办 恐怕只能修改CreateDiv了吧 // 代理类只负责单例模式,不影响原来的类,而且也很透明 let CreateDiv = function (name) { this.name = name this.init() } CreateDiv.prototype.init = function () { let div = document.createElement( 'div' ) // .... } let ProxyCreatInstance = (function () { let instance = null return function (name) { if (!instance) { instance = new CreateDiv(name) } return instance // 返回了实例 } })() let lee1 = new ProxyCreatInstance('lee1') // 直接new 很透明,创建多个还用CreateDiv类 let lee2 = new ProxyCreatInstance('lee2') lee1 === lee2 // true // 这里涉及到了new操作符原理,构造函数如果返回了引用类型,会覆盖掉产生的对象 function mockNew (func, params) { let obj = {} obj.__proto__ = func.prototype let res = func.call(obj, params) return res instanceof Object ? res : obj }
js惰性单例(上面的都混入了传统面向对象语言的特点,没有利用js的特点)
// js嘛 函数是一等公民, 先写个通用的单例函数 function getSingle (fn) { let instance = null return function () { return instance || (instance = fn.call(this, arguments)) // fn有返回 这样写更简洁 } } let CreateDiv = function (name) { this.name = name let div = document.createElement( 'div' ) div.setAttribute('id', name[0]) return div } let singleton = getSingle(CreateDiv) let lee1 = singleton('lee1') let lee2 = singleton('lee2') lee1 === lee2 // true button.addEventListener('click', function () { let lee = singleton('lee') } , false);
小结:不透明模式需要了解代码,不够友好,基于传统的面向对象,我们可以通过代理类来实现单例模式的透明化,创建对象和管理单例的职责被分布在两个不同的方法中。惰性单例技术就是指在合适的时候才创建对象。
代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。当我们不方便直接访问对象本体的时候,通过代理去访问它。
比如我们上面单例模式中的代理类ProxyCreatInstance,我们如果不用代理,就像第三种单例类一样,代码耦合在一起,假如我们想创建100个div,只能去更改CreateDiv的内部,因为你new 100单例模式下个它也只有一个。用了代理模式后,当你需要用单例的时候走代理类就ok了,不影响原来的类。
小王要给小芳送花,他不善言辞,送花失败率高,他让小芳的朋友小明替他去送,小明挑了一个小芳开心的日子去送花,一下车替小王解决了终身大事。。。小明这个代理可以监听小芳的心情,在她心情好的时候再去送,这就是代理模式的作用。
function Flower () {}
let xiaoWang = {
sendFlower: function (target) {
let flower = new Flower()
target.receiveFlower(flower)
}
}
let xiaoMing = {
receiveFlower: function (flower) {
xiaoFang.listenGoodMood(function () {
xiaoFang.receiveFlower(flower)
}
})
}
let xiaoFang = {
receiveFlower: function (flower) {
console.log('收到花')
},
listenGoodMood: function (fn) {
setTimeout(fn, 10000)
}
}
xiaoWang.sendFlower(xiaoMing)
策略模式
定义一系列的算法,将不变的部分和变化的部分隔开,实际就是将算法的使用和实现分离出来,算法的使用方式是不变的
举个栗子
A级 奖金为工资*3
B级 奖金为工资*2
C级 奖金为工资*1
D级 奖金为0
function calculateBonus ( performanceLevel, salary ){
if ( performanceLevel === 'A' ){
return salary * 3;
}
if ( performanceLevel === 'B' ){
return salary * 2;
}
if ( performanceLevel === 'C' ){
return salary * 1;
}
};
calculateBonus( 'A', 10000 );
calculateBonus( 'B', 5000 );
---------------------------------------------
const strategies = {
'A': function (salary) {
return salary * 3;
},
'B': function (salary) {
return salary * 2
},
'C': function (salary) {
return salary * 1
},
'D': function (salary) {
return salary * 0
}
}
const calculateBonus = function (performanceLevel, salary) {
return strategies[performanceLevel](salary)
}
calculateBonus( 'A', 10000 );
calculateBonus( 'B', 5000 );
发布订阅模式
它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。例如你订阅了一些情报,情报中心刚才发布了一些新消息,如果里面有你订阅的情报时,就会通知到你。发布订阅的好处是发布和订阅是完全解耦的,它们通过消息类型关联。
像js中的addEventListener、vue中的bus通信都是用的发布订阅模式。
button.addEventListener("click", function () {}, false);
// html
// js
/**
* 发布订阅模式 发布订阅互不影响,通过消息类型关联
*/
var Observe = function () {
// 定义消息队列
this._msgQueue = {}
}
Observe.prototype = {
// 消息订阅
subscribe: function (type, fn, msg) {
if (this._msgQueue[type] === void (0)) {
this._msgQueue[type] = [fn]
} else {
this._msgQueue[type].push(fn)
}
alert(msg)
},
// 发布
publish: function (type, args) {
console.log(this._msgQueue[type])
if (!this._msgQueue[type]) return
let params = {
type: type,
args: args || {}
}
let i = 0, len = this._msgQueue[type].length;
for (; i < len; i++) {
this._msgQueue[type][i].call(this, params)
}
},
// 移除消息订阅
off: function (type, fn) {
if (this._msgQueue[type] instanceof Array) {
let i = 0, len = this._msgQueue[type].length;
for (; i < len; i++) {
if (this._msgQueue[type][i] === fn) {
this._msgQueue[type].splice(i, 1)
return
}
}
}
}
}
// -----------------------------------
var Ming = new Observe()
function handleOn(type) {
console.log('type', type);
switch (type) {
case 'm1':
console.log('m1');
let fun1 = function (params) {
alert(params.args)
}
Ming.subscribe(type, fun1, '放学了-订阅成功')
break;
case 'm2':
let fun2 = function (params) {
alert(params.args)
}
Ming.subscribe(type, fun2, '老师来了-订阅成功')
break;
case 'm3':
let fun3 = function (params) {
alert(params.args)
}
Ming.subscribe(type, fun3, '打一把王者-订阅成功')
break;
}
}
function handleSub(type) {
switch (type) {
case 'm1':
Ming.publish(type, '放学了,快回家找妈妈')
break;
case 'm2':
let fun2 = function (params) {
alert(params.args)
}
Ming.publish(type, '老师来了-快把手机藏起来')
break;
case 'm3':
let fun3 = function (params) {
alert(params.args)
}
Ming.publish(type, '同学们请注意,这堂课我们一起玩王者农药')
break;
}
}
中介者模式
中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可
缺点:系统中会新增一个中介者对象,增加了对象之间交互的复杂性,中介者对象经常是巨大的,有时候难以维护。