最近公司晋升岗位里面的一道小题《使用 es3 实现简单工厂模式、单例模式、观察者模式、发布订阅模式》,结束之后发现自己还要补好多´д` ;,抽空把里面几道小题,重新复习了一遍,扩充到es3、es5、es6,并做了笔记(再次膜拜公司出题大佬)。
完整版github代码地址:https://github.com/xuhuihui/smallDemo/blob/master/demo/demo3-设计模式/设计模式.md
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
适用于复杂逻辑判断的情况,例如购物的商品,可以有上架中、出售中、下单中、出货中、派送中、到手等一系列复杂逻辑判断。
关于复杂逻辑的判断,有两种解决方案,策略模式和工厂模式。
1、策略模式vs工厂模式的区别
2、策略模式:JavaScript 复杂判断的更优雅写法
es3代码
var createPop = function (type, text) {
// 创建一个对象,并对该对象做出扩展
var o = new Object();
o.content = text;
o.show = function () {
// 显示相同部分
alert(type + ':' + this.content);
switch (type) {
case 'alert':
// 警示框差异部分
break;
case 'confirm':
// 确认框差异部分
break;
case 'prompt':
// 提示框差异部分
break;
}
};
return o;
};
es6代码
class Image {}
class Link {}
class Text {}
class ElementFactory {
createElement(type, option){
const ELEMENT = {
"image" : Image,
"text" : Text,
"link" : Link
}
let ElementConstructor = ELEMENT(type),
element = null;
if(ElementConstructor){
element = new ElementConstructor(option);
}
return element;
}
}
适用于弹框的实现, 全局缓存。
实现弹框的一种做法是先创建好弹框, 然后使之隐藏, 这样子的话会浪费部分不必要的 DOM 开销, 我们可以在需要弹框的时候再进行创建, 同时结合单例模式实现只有一个实例, 从而节省部分 DOM 开销。
es3代码
简易版代码
1、优点:利用initialized属性,保持永远只创建一个EasySingletonIns实例,
2、缺点:不够封闭,可以从外部访问修改initialized属性。
var EasySingletonIns = {
initialized: false,
// 实例扩展写在此处:
belongTo: undefined,
getBelongTo: function () {
return EasySingletonIns.belongTo;
}
};
var EasySingleton = {
getInstance: function () {
if (EasySingletonIns.initialized) {
return EasySingletonIns;
}
EasySingletonIns.initialized = true;
// 实例扩展也可写在此处:
EasySingletonIns.belongTo = 'EasySingleton';
return EasySingletonIns;
}
};
进阶版代码
1、优点:利用函数的闭包,阻止了内部属性被改变。
2、缺点:不好动态地传入想要的属性。
var AdvancedSingleton = (function () {
var ins = null;
return function () {
if (ins) {
return ins;
}
if (!(this instanceof AdvancedSingleton)) {
return new AdvancedSingleton();
}
function Inner() {
// 实例扩展写在此处:
this.belongTo = 'AdvancedSingleton @ constructor'; }
Inner.prototype = this.constructor.prototype;
// 原型扩展也可写在此处:
ins = new Inner();
return ins;
};
})();
酷炫版代码
1、优点:利用apply,可以给CoolSingletonCreator实例的prototype增加方法。
2、缺点:构造函数返回非自身实例的情况下,会出现问题。
function CoolSingletonCreator(fn) {
if (typeof fn !== 'function') {
throw new Error('CoolSingletonCreator fn param must be function!');
}
var AdvancedSingletonForCool = (function () {
var ins = null;
return function () {
if (ins) {
return ins;
}
if (!(this instanceof AdvancedSingletonForCool)) {
return new AdvancedSingletonForCool();
}
var args = arguments;
function Inner() {
fn.apply(this, args);
}
Inner.prototype = this.constructor.prototype;
ins = new Inner();
return ins;
};
})();
AdvancedSingletonForCool.prototype = fn.prototype;
AdvancedSingletonForCool.getInstance = function () {
// 动态参数:(另外,针对只支持es3语法的浏览器的bind方法是没有的,需要自己实现,不在此处展开)
var args = [null];
return new (Function.prototype.bind.apply(AdvancedSingletonForCool, args.concat.apply(args, arguments)))();
};
return AdvancedSingletonForCool;
}
es5代码
var Singleton = function(name) {
this.name = name;
//一个标记,用来判断是否已将创建了该类的实例
this.instance = null;
}
// 提供了一个静态方法,用户可以直接在类上调用
Singleton.getInstance = function(name) {
// 没有实例化的时候创建一个该类的实例
if(!this.instance) {
this.instance = new Singleton(name);
}
// 已经实例化了,返回第一次实例化对象的引用
return this.instance;
}
es6代码
class Singleton {
constructor(name) {
this.name = name;
this.instance = null;
}
// 构造一个广为人知的接口,供用户对该类进行实例化
static getInstance(name) {
if(!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
}
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
1、当观察的数据对象发生变化时, 自动调用相应函数。比如 vue 的双向绑定;
2、每当调用对象里的某个方法时, 就会调用相应'访问'逻辑。比如给测试框架赋能的 spy 函数;
es3代码
1、方法:使用prototype绑定函数实现。
var Subject = (function () {
// 观察者列表(不是必须的,可以由Subject自己处理)
function ObserverList () {
this.observerList = [];
}
ObserverList.prototype.add = function (observer) {
return this.observerList.push(observer);
};
ObserverList.prototype.remove = function (observer) {
this.observerList = this.observerList.filter(function (item) {return item !== observer;});
};
ObserverList.prototype.count = function () {
return this.observerList.length;
};
ObserverList.prototype.get = function (index) {
return this.observerList[index];
};
// 主题
function Subject () {
this.observers = new ObserverList();
}
Subject.prototype.addObserver = function (observer) {
this.observers.add(observer);
};
Subject.prototype.removeObserver = function (observer) {
this.observers.remove(observer);
};
Subject.prototype.notify = function () {
var observerCount = this.observers.count();
for (let i = 0; i < observerCount; i++) {
var observer = this.observers.get(i);
observer.update.apply(observer, arguments);
}
}
return Subject;
})();
es5代码
1、方法:使用 Object.defineProperty(obj, props, descriptor) 实现观察者模式。
2、缺点:Object.defineProperty() 不会监测到数组引用不变的操作(比如 push/pop 等);Object.defineProperty() 只能监测到对象的属性的改变, 即如果有深度嵌套的对象则需要再次给之绑定 Object.defineProperty();
const data = {}
const input = document.getElementById('input')
Object.defineProperty(data, 'text', {
set(value) {
input.value = value
this.value = value
}
})
input.onchange = function(e) {
data.text = e.target.value
}
es6代码
1、方法:Proxy/Reflect 是 ES6 引入的新特性, 也可以使用其完成观察者模式。
2、优点:可以劫持数组的改变;defineProperty 是对属性的劫持, Proxy 是对对象的劫持;
var obj = {
value: 0
}
var proxy = new Proxy(obj, {
set: function(target, key, value, receiver) {
Reflect.set(target, key, value, receiver)
}
})
proxy.value = 1 // 调用相应函数
定义:在观察者模式中间,增加消息代理进行通信,来实现更更松的解耦。

发布订阅模式和观察者模式的差异:
1、在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
2、在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
3、观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
4、观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。
1、范围:MVC、MVVC的架构、Vue的源码实现和一些小游戏等。
2、优点: 在异步编程中实现更深的解耦。
3、缺点: 创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息以后,可能此消息最后都未发生,但是这个订阅者会始终存在于内存中。如果程序中大量使用发布-订阅的话,也会使得程序跟踪bug变得困难。
es5代码
var Event = function() {
this.obj = {}
}
Event.prototype.on = function(eventType, fn) {
if (!this.obj[eventType]) {
this.obj[eventType] = []
}
this.obj[eventType].push(fn) // 推入数组
}
Event.prototype.emit = function() {
var eventType = Array.prototype.shift.call(arguments)
var arr = this.obj[eventType]
for (let i = 0; i < arr.length; i++) { //推出调用函数
arr[i].apply(arr[i], arguments)
}
}
var ev = new Event()
ev.on('click', function(a) { // 订阅函数
console.log(a) // 1
})
ev.emit('click', 1) // 发布函数
es6代码
class Subject {
constructor() {
this.subs = []
this.state = '张三' // 触发更新的状态
}
getState() {
return this.state
}
setState(state) {
if (this.state === state) {
// 发布者一样
return
}
this.state = state
this.notify() // 有更新,触发通知
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
const idx = this.subs.findIndex(i => i === sub)
if (idx === -1) {
// 不存在该观察者
return
}
this.subs.splice(idx, 1)
}
notify() {
this.subs.forEach(sub => {
sub.update() // 与观察者原型方法update对应!
})
}
}
// 观察人,相当于订阅者
class Observer {
update() {
console.log('update')
}
}
// 测试代码
const subject = new Subject()
const ob = new Observer()
const ob2 = new Observer()
ob2.update = function() {
//修改update方法,实现不同逻辑
console.log('laifeipeng')
}
//目标添加观察者了
subject.addSub(ob)
subject.addSub(ob2)
//目标发布消息调用观察者的更新方法了
// subject.notify(); // 不使用手动触发,通过内部状态的设置来触发
subject.setState('李四')
了解到的设计模式如下(゚o゚;;,以后每周有时间大概会补的吧(・_・;。
设计模式分为三种类型,共24种。