勿忘初心

JavaScript常用设计模式总结

一万个人心中,有一万哈姆雷特。JavaScript虽然是弱类型语言,但正因于此造就了JavaScript的灵活性和创造型。不同的人写实现同一个需求,可能实现方式都不相同,可以写的很简单,也可以很复杂,甚至可以使用设计模式来构建你的代码。

写在前面,关于这篇文档的目的在于总结梳理,易于后期温故知新。

JavaScript中使用设计模式的原因

关于在JavaScript中使用设计模式的原因有三点:

  • 可维护性、复用性:适当的使用设计模式有助于降低代码的耦合度,使代码的复用和项目后期代码的重构变得更加容易,以及对”喜闻乐见”的需求变更更好的实现。
  • 团队协作:设计模式可以更好的组织项目代码结构,使得团队成员之间的工作任务依赖性降低。
  • 性能:合理使用设计模式,可以大幅度减少代码量,提高程序的运行速度。
  • 备注:实现设计模式容易,但是对于应该在什么时候使用什么模式却比较困难,盲目的套用设计模式,反而会使得项目复杂度提高,性能较低,造成过度设计。

单体模式/单例模式

描述:单例模式属于创建型模式,用来限制对象的初始化,并且产生一个唯一实例,可以确保所有代码使用的是都是同一份资源。

优点:可以用来划分命名空间,减少全局变量数目。

场景:全局遮罩层。loading等全局组件。全局配置信息或者应用中比较唯一的信息。

案例代码:创建div

  • 实现方式一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var createDiv = (function(){
    var div;
    return function(){
    if(!div){
    div = document.createElement("div");
    div.innerHTML = "弹出框内容";
    div.style.display = "none";
    document.body.appendChild(div);
    }
    return div;
    }
    });
    document.body.onclick = function(){
    var div = createDiv();
    div.style.display = 'block';
    }
  • 实现方式二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var createDiv = (function(){
    var div = document.createElement("div");
    div.innerHTML = "弹出框内容";
    div.style.display = "none";
    document.body.appendChild(div);
    return div;
    }
    //通用单例模式
    var getInstance = function(fn){
    var ret;
    return function(){
    return ret || (ret = fn.call(this,arguments));
    }
    }
    var divInstance = getInstance(createDiv);
    document.body.onclick = function(){
    var div = divInstance();
    div.style.display = 'block';
    }

代理模式

描述:代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。 代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身 对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之 后,再把请求转交给本体对象。可以提前过滤一些请求,对情况预先处理。

优点:安全代理:用于控制不同权限的对象对目标对象的访问。但是js无法判断谁访问了某个对象,实现困难。

场景:1.虚拟代理实现图片预加载: 常见的做法是先用一张loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里。 2.虚拟代理合并HTTP请求: 我们可以通过一个代理函数proxySynchronousFile 来收集一段时间之内的请求, 最后一次性发送给服务器。比如我们等待2 秒之后才把这2 秒之内需要同步的文件ID 打包发给 服务器,如果不是对实时性要求非常高的系统,2 秒的延迟不会带来太大副作用,却能大大减轻 服务器的压力。 3.虚拟代理在惰性加载中的应用: 首先创建一个虚拟代理对象,接口和本体对象一致,缓存所有用户的操作,当用户需要使用的时候,加载 本地对象,遍历缓存队列实现操作。 4.缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参 数跟之前一致,则可以直接返回前面存储的运算结 缓存代理的例子——计算乘积 5.缓存代理用于ajax异步请求数据:分页缓存已经请求过的数据

案例代码: 图片的预加载一个默认图片

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
(function(w){
//傻瓜实现模式
function imgLoad(src){
var imgDom = document.createElement('img');
imgDom.src = 'chrome-search://local-ntp/images/google_logo.png@2x';
document.body.appendChild(imgDom);
var img = new Image();
img.src = src;
img.onload = function(){
setTimeout(function(){
imgDom.src = src;
},2000)
}
}
imgLoad('chrome-search://local-ntp/images/close_3_mask.png');
//单例实现模式
var imgLoad1 = (function (){
var imgDom = document.createElement('img');
document.body.appendChild(imgDom);
var img = new Image();
img.onload = function(){
setTimeout(function(){
imgDom.src = img.src;
},2000)
}
return {
setSrc : function(src){
imgDom.src = 'chrome-search://local-ntp/images/google_logo.png@2x';
img.src = src;
}
}
})();
imgLoad1.setSrc("chrome-search://local-ntp/images/close_3_mask.png");
//代理实现模式
var imgLoad2 = (function (){
var imgDom = document.createElement('img');
document.body.appendChild(imgDom);
var img = new Image();
img.onload = function(){
setTimeout(function(){
imgDom.src = img.src;
},2000)
}
return {
setSrc : function(src){
imgDom.src = 'chrome-search://local-ntp/images/google_logo.png@2x';
img.src = src;
}
}
})();
var proxySetSrc = (function(){
return {
setSrc : function(src){
imgLoad2.setSrc(src);
}
}
})()
proxySetSrc.setSrc('chrome-search://local-ntp/images/close_3_mask.png');
//上面代理模式原理是这样,但是责任分离的不够单一,为了单一职责,如下:
var imgLoad3 = (function (){//这里只创建一张图片,并对外开发一个设置src的接口
var imgDom = document.createElement('img');
document.body.appendChild(imgDom);
return {
setSrc : function(src){
imgDom.src = src;
}
}
})();
//在代理器中添加额外逻辑,这样创建图片和设置图片就单一职责了
var proxySetSrc1 = (function(){
var img = new Image();
img.onload = function(){
setTimeout(function(){
imgLoad3.setSrc(img.src);
},2000)
}
return {
setSrc : function(src){
imgLoad3.setSrc('chrome-search://local-ntp/images/google_logo.png@2x');
img.src = src;
}
}
})();
proxySetSrc1.setSrc('chrome-search://local-ntp/images/close_3_mask.png');
})(window)

组合模式

描述:组合模式(Composite)将对象组合成树形结构以表示“部分-整体”的层次结构, 组合模式使得用户对单个对象和组合对象的使用具有一致性。也可以理解位用多个小对象组成一个大对象,成树形结构,树的叶对象和整个组合对象具有一致接口。

优点:组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以像处理简单元素一样来处理复杂元素。

场景:你想表示对象的部分整体层次结构,希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

案例代码:遥控器的宏

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
(function(){
//先用面向对象的方式实现
function command(){}
command.prototype.execute = function(){
throw new Error('不能实例化,在子类中重载');
}
function openCommand(){}
openCommand.prototype = new command();
openCommand.prototype.execute = function(){
console.log('开机');
}
function closeCommand(){}
closeCommand.prototype = new command();
closeCommand.prototype.execute = function(){
console.log('关机');
}
function stopCommand(){}
stopCommand.prototype = new command();
stopCommand.prototype.execute = function(){
console.log('暂停');
}
function nextCommand(){}
nextCommand.prototype = new command();
nextCommand.prototype.execute = function(){
console.log('下一个');
}
function unionCommand(){
this.commands = [];
}
unionCommand.prototype = new command();
unionCommand.prototype.add = function(command){
if(this.commands.indexOf(command) == -1){
this.commands.push(command);
}
}
unionCommand.prototype.execute = function(){
this.commands.map(function(command,i){
command.execute();
});
}
var open = new openCommand();
var close = new closeCommand()
var stop = new stopCommand();
var next = new nextCommand();
var union = new unionCommand();
union.add(open);
union.add(close);
union.add(stop);
union.add(next);
union.execute();
//下面用js函数式方式实现
var unionCommand1 = {
commands : [],
add : function(obj){
for (var i in obj) {
this.commands.push(obj[i]);
}
},
execute : function(){
this.commands.map(function(command,i){
command.execute();
});
}
}
var commands1 = {
openCommand : {
execute : function(){
console.log('开机');
}
},
closeCommand : {
execute : function(){
console.log('关机');
}
},
stopCommand : {
execute : function(){
console.log('暂停');
}
},
nextCommand : {
execute : function(){
console.log('下一个');
}
},
}
unionCommand1.add(commands1);
unionCommand1.execute();
})()

建造者模式

描述:将一个复杂对象的构造和形态分离,使用同样的创建过程,不同的表示方法,即建造者模式。 建造者作为director必须拥有创建技能所有方法,而具体的建造技能由builder去完成,建造者指挥 不同的builder去完成建造,最终得出结果。

优点:拆分复杂的流程,易于扩展,指导者类可以随时变为一个新的业务指挥者。

场景:产品和建造类比较稳定,业务逻辑主要封装在指挥者类中。

案例代码:

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
(function(){
function step1(msg){
this.builder = msg;
}
step1.prototype.do = function(){
console.log(this.builder);
}
function step2(msg){
this.builder = msg;
}
step2.prototype.some = function(){
console.log(this.builder);
}
function step3(msg){
this.builder = msg;
}
step3.prototype.sth = function(){
console.log(this.builder);
}
function dosomesth(){
this.s1 = new step1("先打地基");
this.s2 = new step2("再建骨架");
this.s3 = new step3("最后封顶");
}
dosomesth.prototype.do =function(){
this.s1.do();
}
dosomesth.prototype.some =function(){
this.s2.some();
}
dosomesth.prototype.sth =function(){
this.s3.sth();
}
dosomesth.prototype.start = function(){
this.do();
this.some();
this.sth();
}
var director = new dosomesth();
director.start();
})()

先这样,待续


欢迎转载,但转载请注明来自”勿忘初心”的博客,并保持转载后文章内容的完整性。

本文链接:http://lucylc.com/设计模式/2018/04/16/JavaScript常用设计模式总结.html

坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章