几种设计模式

单列模式

单列模式 适合登录框这种场景或者需要全局通信的场景,不同页面调用其实都的是一个对象
这样不会生成多个实例造成混乱

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{
//es6 单列模式
class Login{
constructor(state) {

}
login() {
console.log('....login')
}
}
//定义一个静态方法,这个方法是唯一的
Login.getInstance = (function(){
let instance;
return function() {
if(!instance) {
instance = new Login()
}
return instance
}
})()

//测试
let obj1 = new Login.getInstance();
obj1.login();

let obj2 = new Login.getInstance();
obj2.login();


console.log(obj1 === obj2) // ...login ...login true

let obj3 = new Login()
console.log(obj1 === obj3) //false 没有走静态方法,不可控


}




{
//ES5 单列
//对象字面量
var Login = {
status : false,
login:function() {
if(this.status == true) {
console.log('已经登陆了')
return
}
this.status = true;
console.log('....login')
},
logout:function() {
if(this.status == false) {
console.log('已经登出了')
return
}
this.status = false;
console.log('....logout')
}
}

var o1 = Login;
o1.login(); //login....

var o2 = Login
o2.login() //已经登录了
console.log(o1 === o2) //true
}



工厂模式

下面这个模拟 JQUERY 的其实就是个工厂模式 ,JQuery 这个大类提供 API
我们用的时候直接使用 window 上挂载的 $ 这个就相当于 JQuery 的工厂
不必关心工厂是怎么实现的

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
class jQuery {
constructor(selector) {
let slice = Array.prototype.slice
let dom = slice.call(document.querySelectorAll(selector))
let len = dom ? dom.length : 0
for (let i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
append(node) {

}
addClass(name) {

}
html(data) {

}
// 此处省略若干 API
}
window.$ = function (selector) {
return new jQuery(selector)
}
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
{
var carShop = function() {

};


carShop.prototype = {
sell: function(model) {
var car;
switch (model) {
case "1":
car = new Audi();
break;

case "2":
car = new Lowrider();
break;

case "3":
car = new Cruiser();
break;
default:


}

return car;

}

}
var ABC = new carShop();
var myCar = ABC.sell("1");

}

发布订阅者模式

观察者模式特点:
1,发布订阅
2,1 对多

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

{
//es6
//主题,保存状态,状态发生变化触发所有观察者对象
class Subject {
constructor() {
//要变化的状态
this.state = 0;
//观察者列表
this.observers = []
}
getState () {
return this.state
}
setState(state) {
this.state = state;
//状态变化通知所有观察者
this.notifyAllObservers()
}
notifyAllObservers() {
this.observers.forEach(observer => {
observer.update()
})
}
add(observer) {
//添加新的观察者
this.observers.push(observer)
}
}

//观察者
class Observer {
constructor(name,subject) {
this.name = name;
this.subject = subject;
//初始化观察者的时候把自身加入subject的观察者列表
this.subject.add(this)
}

update() {
console.log(`${this.name} update! state : ${this.subject.getState()}`)
}
}

//测试
let subject = new Subject() //生成一个新主题
let o1 = new Observer('weibin', subject)
let o2 = new Observer('xiaoming', subject)

subject.setState(0) //修改了主题 触发所有观察者
subject.setState(1)


}

{

//卖房的列子

<script>
let houseObj = {}; //房产中介 (发布者)

//房产中介的花名册登记谁要买房 (缓存列表)
//定义成数组 最后形式是[key1:fn,ke2:fn,ke3:fn]
//如果定义成对象,取得时候会有深拷贝的问题。
houseObj.list = [];

//增加订阅消息的顾客
houseObj.listen = function(key, fn) {
//如果没有key 一旦发布者发布一个消息,所有的订阅者都会收到消息,不论他们是否订阅了该消息
//如果标识在缓存列表里没有 则创建一个空数组占位
//[key1:fn,key2:fn,fn...]
//key1:fn 代表有标识 如果只有一个FN 无标识就return
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
console.log(this.list);
//简写
// (this.list[key] || (this.list[key] = [])).push(fn);
};

//发布消息
houseObj.trigger = function() {
//取出消息名称key
let key = Array.prototype.shift.call(arguments);
//取出的FNS是一个数组
let fns = this.list[key];

if (!fns || fns.length === 0) {
return;
}

fns.forEach(v => {
v.apply(this, arguments);
});
};

houseObj.remove = function(key, fn) {
var fns = this.list[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i];
if (_fn === fn) {
fns.splice(i, 1);
}
}
}
};

//订阅
houseObj.listen(
"a",
(fn1 = function(size) {
console.log(`小红要买${size}平米`);
})
);

houseObj.listen(
"b",
(fn3 = function(size) {
console.log(`小明要买${size}平米`);
})
);

houseObj.trigger("a", 100); //小红要买100平米
houseObj.trigger("b", 150); //小明要买150平米

houseObj.remove("a", fn1);
houseObj.trigger("a", 100); // 无打印输出
</script>

}



{
//jquery

var callbacks = $.Callbacks();

//添加观察者
callbacks.add(function(info) {
console.log('观察者1',info)
})

callbacks.add(function(info) {
console.log('观察者2',info)
})


callbacks.fire('gogogo') //观察者1 gogogo 观察者2 gogogo
}


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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<!DOCTYPE html>

<head>
<title></title>
<meta charset="UTF-8">
<style>


</style>
</head>

<body>


<div id="test">
<div style="width: 500px; height: 300px;">
<button id="btn1">改变城市1</button>
</div>
<div style="width: 500px; height: 300px;">
<button id="btn2">改变城市2</button>
</div>


</div>
<script>
// 功能:两个模块分别有两个城市切换按钮,切换其中一个,另一个模块得到通知跟着改变
;(function(win,doc){
function Subject() {
this.state = 0;
//观察者列表
this.observers = []
}
Subject.prototype = {
getState: function() {
return this.state
},
setState: function(state) {
this.state = state;
//状态变化通知所有观察者
this.notifyAllObservers()
},
notifyAllObservers:function() {
this.observers.forEach(observer => {
observer.update()
})
},
add:function(observer) {
//添加新的观察者
this.observers.push(observer)
}
}
function Observer(subject,fn,name) {
this.subject = subject;
this.fn = fn;
this.name = name;
this.subject.add(this)
}
Observer.prototype.update = function() {
this.fn[this.name](this.subject.getState())
}
var subject = new Subject()

window.subject = subject;
window.Observer = Observer;
})(window,document)

;(function(win,doc){
function A() {
this.btn = document.getElementById("btn1");
//默认城市ID是1
this.render(1);
this.click();
//注册自己为观察者,
//传入参数1:主题(写死subject就可以,已经挂在全局)
//传入参数2:自己
//传入参数3:自己的渲染方法名
new Observer(subject,this,'render')
}
A.prototype = {
click:function() {
var _this = this;
this.btn.addEventListener("click",function(){
//模拟接口
setTimeout(function(){
//主题变化,通知所有观察者重新渲染
subject.setState(2)
},500)
},false);
},
render:function(id) {
setTimeout(function(){
//渲染数据
console.log("A模块的ID是" + id)
},500)
}
}

new A()

})(window,document)

;(function(win,doc){
function B() {
this.btn = document.getElementById("btn2");
//默认城市ID是1
this.render(1);
this.click();
new Observer(subject,this,'render')
}
B.prototype = {
click:function() {
var _this = this;
this.btn.addEventListener("click",function(){
//模拟接口
setTimeout(function(){
subject.setState(3)
},500)
},false);
},
render:function(id) {
setTimeout(function(){
//渲染数据
console.log("B模块的ID是" + id)
},500)
}
}

new B()

})(window,document)





</script>
</body>

</html>

代理模式

代理模式 可以改变数据 –购物车https://github.com/weibsgz/design-cart/blob/master/src/demo/List/CreateItem.js

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


// 明星
let star = {
name : '魏彬',
age:25,
phone:'13401095555'
}
//代理
let agent = new Proxy(star,{
get : function(target,key) {
if(key === 'phone') {
//返回经纪人自己的手机号
return '13521497119'
}
if(key === 'price') {
//明星不报价
return 1000000
}

return target[key] //其余姓名,年龄可以返回明星的
},

set: function(target,key,val) {
if(key == 'customPrice') {
if (val < 1000000) {
throw new Error('价格太低了')
}
else {
target[key] = val;
return true;
}
}
}
})


console.log(agent.name, agent.age, agent.price, agent.phone) //魏彬 25 1000000 13521497119


agent.customPrice = 90;

console.log(agent.customPrice ) //价格太低了

策略模式

策略模式的优点:

  1. 策略模式可以有效避免很多 if 条件语句
  2. 策略模式符合开放-封闭原则,使代码更容易理解和扩展
  3. 策略模式中的代码可以复用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var sObj = {
"A":function(salary) {
return salary * 4;
},
"B":function(salary) {
return salary * 2
},
"C":function(salary) {
return salary
}
}

var cal = function(level,salary) {
return sObj[level] && sObj[level](salary)
}
console.log(cal('A',4000))

状态模式

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
//状态模式,避免大量IF-ELSE
//

function SuperMary() {
this.state = [];
this.fn = {
jump: function() {
console.log("jump");
},
move: function() {
console.log("move");
},
shoot: function() {
console.log("shoot");
}
};
}

SuperMary.prototype.changeState = function(action) {
//每次调用change 可以先清下数组,确保每次chageState都是新的状态
this.state = [];
if (typeof action === "string") {
this.state.push(action);
} else if (action instanceof Array) {
this.state = this.state.concat(action);
}
return this;
};

SuperMary.prototype.run = function() {
this.state.forEach(state => {
this.fn && this.fn[state]();
});
return this;
};

new SuperMary()
.changeState(["jump", "move"]) //jump move
.run()
.changeState("shoot") //shoot
.run();