谈谈设计模式


多态

多态的思想是把”做什么”和“谁去做”分开来

多态最根本的作用是通过过程化的条件分支语句转化为对象的多态性 从而消除这些条件分支语句

将行为分布在各个对象中,并让这些对象各自负责自己的行为,这正是面向对象设计的优点。

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
//不用多态的版本
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};
var baiduMap = {
show: function(){
console.log( '开始渲染百度地图' );
}
};
var renderMap = function( type ){
if ( type === 'google' ){
googleMap.show();
}else if ( type === 'baidu' ){
baiduMap.show();
}
};
renderMap( 'google' ); // 输出:开始渲染谷歌地图
renderMap( 'baidu' ); // 输出:开始渲染百度地图
//不断地在renderMap中堆砌条件分支语句 导致代码臃肿
var renderMap = function( map ){
if ( map.show instanceof Function ){
map.show();
}
};
//使用多态 只需要关注发出show的动作 不必关心是谁的show做什么动作

原型模式

  • 所有数据都是对象
  • 要得到一个对象 不是通过实例化一个类 而是找到一个对象并作为原型克隆它
  • 对象会记住它的原型
  • 如果对象无法响应某个请求 就会把这个请求委托给它自己的原型

this、call、apply

this

this 总是指向一个对象 而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的 而非函数声明的环境

闭包

使用闭包的同时容易形成循环引用 如果闭包的作用域链中存在一些 DMO 节点 就可能造成内存泄漏

要解决循环引用带来的内存泄露问题 只需要讲循环引用中的变量设为 null 即可

设计模式

单例模式

保证一个类仅有一个实例 并提供一个访问它的全局访问点 即单例模式的核心是确保只有一个实例并提供全局访问

作为开发者应该尽量减少全局变量的使用 可以使用闭包封装私有变量来避免对全局的命令污染

1
2
3
4
5
6
7
8
9
var user = (function(){
var __name = 'sven',
__age = 29;
return {
getUserInfo: function(){
return __name + '-' + __age;
}
}
})();

策略模式

定义:定义一系列的算法 把它们一个一个封装起来 并且使它们可以互相替换 目的是将算法的使用与算法的实现分离开来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
图灵社区会员 轩辕 专享 尊重版权
76 第 5 章 策略模式
}
};
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000

代理模式

保护代理:代理可以帮助过滤掉一些请求

虚拟代理:虚拟代理把一些开销很大的对象延迟到真正需要它的时候才去创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
虚拟代理:
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
imgNode.src = src;
}
}
})();
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage.setSrc( this.src );
}
return {
setSrc: function( src ){
myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
img.src = src;
}
}
})();
proxyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );
//加载中用一张本地图片 加载后才替换为对应url

单一职责原则:一个类(通常包括对象和函数等)而言,应该仅有一个引起它变化的原因 如果一个对象承担的职责过多 就会导致脆弱和低内聚

**代理和本体应该拥有一样的主要逻辑功能 这样用户可以在不需要用到代理的时候直接将请求对象改成本体而无需修改本体的代码 **

虚拟代理合并 http 请求

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
需求场景:同步文件 如果短时间内连续发送请求会导致服务器压力太大 可以设置代理函数收集一段时间内的请求最后一次性发送给服务器 除非是对实时性要求高的系统 不然延迟都不会带来太大的副作用却可以大大减轻服务器的压力
var synchronousFile = function( id ){
console.log( '开始同步文件,id 为: ' + id );
};
var proxySynchronousFile = (function(){
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function( id ){
cache.push( id );
if ( timer ){ // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒后向本体发送需要同步的 ID 集合
clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000 );
}
})();
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};

缓存代理可以为一些开销很大的运算结果提供暂时的储存 在下次运算时 如果传递进来的参数跟之前的一样 就可以直接返回之前的计算结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
先创建一个用于求乘积的函数:
var mult = function(){
console.log( '开始计算乘积' );
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
mult( 2, 3 ); // 输出:6
mult( 2, 3, 4 ); // 输出:24
现在加入缓存代理函数:
var proxyMult = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = mult.apply( this, arguments );
}
})();
proxyMult( 1, 2, 3, 4 ); // 输出:24
proxyMult( 1, 2, 3, 4 ); // 输出:24第二次并没有进行计算 而是直接返回之前计算好的结果 通过增加缓存代理的方式 mult函数可以继续专注于自身的职责-计算乘积 而缓存的功能时由代理对象实现的

缓存代理用于 api 请求

在进行请求一些不变的数据的时候(分页需求)同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后 下次再请求同一页的时候 就可以直接使用之前的数据 可以通过缓存代理实现

使用高阶函数动态创建代理

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
/**************** 计算乘积 *****************/
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
/**************** 计算加和 *****************/
var plus = function(){
var a = 0;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a + arguments[i];
}
return a;
};
/**************** 创建缓存代理的工厂 *****************/
var createProxyFactory = function( fn ){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = fn.apply( this, arguments );
}
};
var proxyMult = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10

tips:编写业务的时候往往不需要预先去猜测是否需要使用代理模式 当真正发现不方便的时候直接访问某个对象的时候 再编写代理也不迟

迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象 的内部表示

发布-订阅模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型 来替代传统的发布—订阅模式。

发布-订阅模式可以广泛用于异步编程中 是一种可以代替回调函数的方案 在异步编程中使用发布-订阅模式 我们就无需过多关注对象在一异步运行期间的内部状态 而只需要订阅感兴趣的事件发生点

发布-订阅模式可以取代对象之间的硬编码的通知方式 一个对象不再显式地调用另一个对象的某个接口实现松耦合 可以单独对订阅者和发布者做修改而不用去修改其引用

1
2
3
4
发布-订阅模式的步骤
1.首先要指定谁充当发布者
2.然后给发布者添加一个缓存列表 用于存放回调函数以便通知订阅者
3.最后在发布消息的时候 发布者会遍历这个缓存列表 依次触发里面存放的订阅者函数 另外 可以往回调函数中加入一些参数 订阅者可以接收参数 这很有必要
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
var event = {
clientList: [],
listen: function( key, fn ){
if ( !this.clientList[ key ] ){
this.clientList[ key ] = [];
}
this.clientList[ key ].push( fn ); // 订阅的消息添加进缓存列表
},
trigger: function(){
var key = Array.prototype.shift.call( arguments ), // (1);
fns = this.clientList[ key ];
if ( !fns || fns.length === 0 ){ // 如果没有绑定对应的消息
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是 trigger 时带上的参数
}
}
};
再定义一个 installEvent 函数,这个函数可以给所有的对象都动态安装发布—订阅功能:
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};
再来测试一番,我们给售楼处对象 salesOffices 动态增加发布—订阅功能:
var salesOffices = {};
installEvent( salesOffices );
salesOffices.listen( 'squareMeter88', function( price ){ // 小明订阅消息
console.log( '价格= ' + price );
});
salesOffices.listen( 'squareMeter100', function( price ){ // 小红订阅消息
console.log( '价格= ' + price );
});
salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000
salesOffices.trigger( 'squareMeter100', 3000000 ); // 输出:3000000
取消订阅的事件
event.remove = function( key, fn ){
var fns = this.clientList[ key ];
if ( !fns ){ // 如果 key 对应的消息没有被人订阅,则直接返回
return false;
}
if ( !fn ){ // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
fns && ( fns.length = 0 );
}else{
for ( var l = fns.length - 1; l >=0; l-- ){ // 反向遍历订阅的回调函数列表
var _fn = fns[ l ];
if ( _fn === fn ){
fns.splice( l, 1 ); // 删除订阅者的回调函数
}
}
}
};
var salesOffices = {};
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
}
installEvent( salesOffices );
salesOffices.listen( 'squareMeter88', fn1 = function( price ){ // 小明订阅消息
console.log( '价格= ' + price );
});
salesOffices.listen( 'squareMeter88', fn2 = function( price ){ // 小红订阅消息
console.log( '价格= ' + price );
});
salesOffices.remove( 'squareMeter88', fn1 ); // 删除小明的订阅
salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000

全局的发布订阅模式

发布订阅模式可以用一个全局的 Event 对象来实现 订阅者不需要了解消息来自哪个发布者 发布者也不知道消息会被推送给哪些订阅者 Event 作为一个类似于中介的角色把订阅者和发布者联系起来

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
var Event = (function(){
var clientList = {},
listen,
trigger,
remove;
listen = function( key, fn ){
if ( !clientList[ key ] ){
clientList[ key ] = [];
}
clientList[ key ].push( fn );
};
trigger = function(){
var key = Array.prototype.shift.call( arguments ),
fns = clientList[ key ];
if ( !fns || fns.length === 0 ){
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments );
}
};
remove = function( key, fn ){
var fns = clientList[ key ];
if ( !fns ){
return false;
}
if ( !fn ){
fns && ( fns.length = 0 );
}else{
for ( var l = fns.length - 1; l >=0; l-- ){
var _fn = fns[ l ];
if ( _fn === fn ){
fns.splice( l, 1 );
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
Event.listen( 'squareMeter88', function( price ){ // 小红订阅消息
console.log( '价格= ' + price ); // 输出:'价格=2000000'
});
Event.trigger( 'squareMeter88', 2000000 ); // 售楼处发布消息

命令模式

组合模式


文章作者: olddog
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 olddog !
  目录