前言
最近公司晋升岗位里面的一道小题《使用 es3 实现简单工厂模式、单例模式、观察者模式、发布订阅模式》,结束之后发现自己还要补好多´д` ;,抽空把里面几道小题,重新复习了一遍,扩充到es3、es5、es6,并做了笔记(再次膜拜公司出题大佬)。
完整版github代码地址:https://github.com/xuhuihui/smallDemo/blob/master/demo/demo3-设计模式/设计模式.md
待做的笔记:
- 实现简易模板字符串(6分)
- 模拟实现es6模板字符串的${}即可;
- 无需考虑特殊符号($、{、})的转义和嵌套等问题;
- 模拟实现简易版React-Redux
简单工厂模式
定义
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
适用范围
适用于复杂逻辑判断的情况,例如购物的商品,可以有上架中、出售中、下单中、出货中、派送中、到手等一系列复杂逻辑判断。
关于复杂逻辑的判断,有两种解决方案,策略模式和工厂模式。
1、策略模式vs工厂模式的区别
2、策略模式:JavaScript 复杂判断的更优雅写法
实现
es3代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var 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代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class 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属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var 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、缺点:不好动态地传入想要的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var 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、缺点:构造函数返回非自身实例的情况下,会出现问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32function 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代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var 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代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14class 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绑定函数实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38var 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();
1
2<input id="input" type="text" />
1
2
3
4
5
6
7
8
9
10
11
12
13const 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 是对对象的劫持;
1
2
3
4
5
6
7
8
9
10var 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代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23var 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代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54class 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('李四')
JavaScript 中常见设计模式
了解到的设计模式如下(゚o゚;;,以后每周有时间大概会补的吧(・_・;。
设计模式分为三种类型,共24种。
- 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
- 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。
最后
以上就是害怕绿茶最近收集整理的关于使用 es3、es5、es6 实现简单工厂模式、单例模式、观察者模式、发布订阅模式前言的全部内容,更多相关使用内容请搜索靠谱客的其他文章。
发表评论 取消回复