design-pattern.md

学习设计模式有什么作用?

[开发]
开发效率
利于团队协作

[维护]
有利于代码的升级改版
逻辑清晰/代码严谨/利于后期维护

[通用]
我们依托设计模式可以实现组件化/模块化/插件化/框架化以及一些常用类库方法的编写

插件、组件、类库、框架的区别

类库:提供一些真实项目开发中常用的方法(方法做了完善处理:兼容处理、细节优化),方便我们开发和维护[jQuery/Zepto…]

插件:把项目中某一部分进行插件封装(是具备具体业务逻辑,更加有针对性),以后再有类似的需求,直接导入插件即可,相关业务逻辑代码不需要自己在编写了[jquery.drag.js/jquery.dialog.js/jquery.validate.min.js/datepicker日历插件/echarts统计图插件/iscroll]

组件:类似于插件,但是插件一般只是把js部分封装了,组件不仅仅封装了js部分,而且把css部分也封装了,以后再使用的时候,我们直接按照文档使用说明引入css/js,搭建对应的结构,什么都不用做功能自然就有了[swiper组件/bootstrap组件…]

框架:比上面三个都要庞大,它不仅仅提供了很多常用的方法,而且也可以支持一些插件的扩展(可以把一些插件集成到框架中运行)、更重要的是提供了非常优秀的代码管理设计思想…[React/Vue/Angular/React Native]

JS中的常用设计模式

单例设计模式、构造原型设计模式、发布订阅设计模式、promise设计模式

单例设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//=> 单例模式:把实现当前这个模块所有的属性和方法汇总到同一个命名空间下(分组作用,避免了全局变量的污染)
let exampleRender = (function(){
//=> 实现当前模块具体业务逻辑的方法全部存放在闭包中
let fn = function(){
//=> ...
}
return {
init: function(){
//=> 入口方法:控制当前模块的具体业务逻辑顺序
fn();
}
}
})();
exampleRender.init();

真实项目中,我们如果想要实现具体的业务逻辑需求,都可以依托于当理模式构建,我们把项目划分成各大板块或者模块,把实现同一个板块的方法放在一个独立的命名空间下,方便团队写作开发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Tool {
constructor(){
this.isCompatible = 'addEventlistener' in document; //=> 如果不兼容返回false(IE6-8)
}
//=> 挂载到原型上的方法
css(){
//=> ...
}
//=> 挂载到普通对象上的方法
static distinct(){
//=>...
}
}
class Banner extends Tool{
constructor(...arg){
supper();
this.xxx = xxx;
}
//=> 挂载到子类原型上的方法
bindData(){
this.css(); //=> 把父类原型上的方法执行(子类继承了父类,那么子类的实例就可以调取父类原型上的方法)
this.distinct === undefined; //=> 子类的实例只能调取父类原型上的方法,以及父类给实例提供的私有属性方法,但是父类作为普通对象假如的静态方法,子类的实例是无法调取的(只有这样才可以调取使用Tool.distinct())
}
}

有三个类A/B/C,让C继承A和B

1
2
3
class A {}
class B extends A{}
class C extends B{}

发布订阅设计模式:观察者模式

不同于单例和构造,发布订阅是小型设计模式,应用到某一个具体的需求中:凡是当到达某个条件之后执行N多方法,我们都可以依托于发布订阅设计模式管理规划我们的JS代码

我们经常把发布订阅设计模式嵌套到其他的设计模式中

promise设计模式

解决AJAX异步请求层级齐嵌套的问题,它也是小型设计模式,目的是为了解决层级齐嵌套问题,我们也会经常把它嵌套再其他的设计模式运行

1
2
3
4
5
6
7
8
9
10
11
12
13
$.ajax({
url: '',
async: true,
success: function(){
$.ajax({
url: '',
async: true,
success: function(){
//=> 还会有后续嵌套
}
})
}
})

发布订阅设计模式

俗称观察者模式

实现思路
1、我们先创建一个计划表(容器)
2、后期需要做什么事情,我们都一次把需要处理的事情增加到计划表中
3、当符合某个条件的时候,我们只需要通知计划表中的方法按照顺序依次执行即可

JQ中的发布订阅

JQ中提供了实现发布订阅设计模式的方法

1
2
3
4
5
6
let $plan = $.Callbacks(); //=> 创建一个计划表

let fn = function(){};
$plan.add(fn); //=> 向计划表中添加方法
$plan.remove(fn); //=> 从计划表中移除方法
$plan.fire(100, 200); //=> 通知计划表中所有的方法按照顺序执行:100 200分别作为实参传递给每一个需要执行的方法
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
~function(){
//=> Each:遍历数组中每一项的内容
let each = function (ary, callback) {
for (let i = 0; i < ary.length; i += 1) {
let result = callback && callback(ary[i], i);
if (result === false) {
//=> 如果执行的回调函数中返回false,代表结束当前正在便利的操作(仿照JQ中的each语法实现)
break;
}

//=> 如果回调函数中返回的是DEL,代表当前这一项在回调函数中被删除了,为了防止数组塌陷问题
if (result === 'DEL') i--;
}
};

class Plan {
constructor() {
this.planList = []; //=> 存放方法的容器
}

//=> 挂在到plan原型上的方法
add(fn) {
let planList = this.planList,
flag = true;

//=> 去重处理
each(planList, function(item, index){
if (item === fn) {
flag = false;
return false;
}
})
flag ? planList.push(fn) : null;
}

remove(fn) {
let planList = this.planList;
each(planList, function(item, index){
if (item === fn) {
//planList.splice(index, 1); //=> 这样会引起数组塌陷
planLis[index] = null;
return false;
}
})
}

fire(...arg) {
let planList = this.planList;
each(planList, function(item, index){
if (item === null) {
planList.splice(index, 1);
return 'DEL';
}
item(...arg);
})
}


//=> 挂在到Plan这个对象上的属性和方法
static Callbacks() {
return new Plan();
}
}

window.$ = window.Plan = Plan;
}();

let $plan = Plan.Callbacks();
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
//=> Each:遍历数组中每一项的内容
let each = function (ary, callback) {
if (!(ary instanceof Array)) return;
for (let i = 0; i < ary.length; i += 1) {
let result = callback && callback(ary[i], i);
if (result === false) {
//=> 如果执行的回调函数中返回false,代表结束当前正在便利的操作(仿照JQ中的each语法实现)
break;
}

//=> 如果回调函数中返回的是DEL,代表当前这一项在回调函数中被删除了,为了防止数组塌陷问题
if (result === 'DEL') i--;
}
};

//=> 第一部分 DOM2事件绑定
~function(){
class EventLibrary {
constructor(){

}
on(curEle, type, fn) {
if (typeof curEle['pond' + type] === 'undefined') {
curEle['pond' + type] = [];
let _run = this.run;
'addEventListener' in document ? curEle.addEventListener(type, _run, false) : curEle.attachEvent('on' + type, function(e){
_run.call(curEle, e);
})
}
let ary = curEle['pond' + type],
flag = true;
each(ary, (item, index) => {
if (item === fn) flag = false;
return flag;
})
flag ? ary.push(fn) : null;
}

off(curEle, type, fn) {
let ary = curEle['pond' + type];
each(ary, (item, index) => {
if (item === fn) {
ary[index] = null;
}
});

}

run(e) {
//=> this当前元素
e = e || window.event;
if (!e.target) {
e.target = e.srcElement;
e.pageX = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
e.pageY = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
e.which = e.keyCode;
e.preventDefault = function(){
e.returnValue = false;
}
e.stopPropagation = function(){
e.cancelBubble = true;
}
}
let ary = this['pond' + e.type];
each(ary, (item, index) => {
if (item === null) {
ary.splice(index, 1);
return 'DEL';
}
item.call(this, e);
})
}

}
window.EventLibrary = EventLibrary;
}();
on(document.body, 'click', fn1);
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
~function(){
Function.prototype.myBind = function myBind (context) {
//=> this:fn(我们需要预先处理this的函数)
//=> context:我们需要预先改变的this值,(如果不传递默认值是window)
context = context || window;
var outerArg = Array.prototypr.slice.call(arguments, 1); //=> 除了context之外传递进来的参数值
var _this = this;
if ('bind' in Function.prototype) {
outerArg.unshift(context);
return _this.bind.apply(_this, outerArg);
}

return function(e){
//=> _this:fn
//=> arguments:返回的匿名函数接收到的参数值
var innerArg = Array.prototype.slice.call(arguments, 0);
outerArg = outerArg.concat(innerArg);
_this.apply(context, outerArg);
}
};

return function(e){
//=> _this:fn
//=> arguments:返回的匿名函数接收到的参数值
var innerArg = Array.prototype.slice.call(arguments, 0);
outerArg = outerArg.concat(innerArg);
_this.apply(context, outerArg);
}
};
class Drag extends EventLibrary {
constructor(curEle) {
super();
this.curEle = curEle;

//=> callback
this.planDown = Plan.Callback();
this.planMove = Plan.Callback();
this.planUp = Plan.Callback()

//=> mousedown
this.on(this.curEle, 'mousedown', () => {
this.down.call(this);
});
}

down() {
//=> this:example
let curEle = this.curEle,
{top:t, left: l} = this.offset();
this.mouseX = e.pageX;
this.mouseY = e.pageY;
this.strL = l;
this.srtT = t;

//=> mousemove / mouseup
this._MOVE = this.move.myBind(this);
this._UP = this.ip.myBind(this);
this.on(document, 'mousemove', this._MOVE);
this.on(document, 'mouseup', this._UP);
this.planDown.fire(this);
}

move(e) {
//=> this:example
let curEle = this.curEle;
let curL = e.pageX - this.mouseX + this.strL,
curT = e.pageY - this.mouseY + this.strT;

//=> 边界判断
let {offsetParent:p} = curEle,
W = document.documentElement.clientWidth || documment.body.clientWidth,
H = document.documentElement.clientHieght || document.body.clientHieght;
if (p.tagName !== 'BODY') {
W = p.clientWidth;
H = p.clientHeight;
}
let maxL = W - curEle.offsetWidth,
maxT = H - curEle.offsetHieght;

curL = curL < 0 ? (curL > maxL ? maxL : curL);
curT = curT < 0 ? (curT > maxT ? maxT : curT);

curEle.style.left = curL + 'px';
curEle.style.top = curL + 'px';

this.planMove.fire();
}

up(e) {
//=> this:example
this.off(document, 'mousemove', this._MOVE);
this.off(document, 'mouseup', this._UP);
this.planUp.fire();
}

offset(flag) {
//=> this:example
let curEle = this.curEle;
let {offsetLeft:l, offsetTop:t, offsetParent:p} = curEle;
if (!flag) {
while (p.tagName !== 'BODY') {
let {clientLeft, clientTop, offsetLeft, offsetTop, offsetParent} = p;
{userAgent} = window.navigator;
if (userAgent.indexOf('MSIE 8') === -1) {
//=> 不是IE8需要加上边框
l += clientLeft;
t += clientTop;
}

l += offsetLeft;
t += offsetTop;
p = offsetParent;
}
}
return {top: t, left: l}

}

static init(curEle, {index = 0} = {}) {
return new Drag(curEle);
}
}
window.d rag = Drag;
}();
Drag.init(oBox, {
index: 0
});