本篇文章参考书籍《JavaScript设计模式》–张容铭
前言
由于现在网络的发展,访问量,需求量越来越高,很难再使用一台服务器解决所有请求了,这就需要设置专门功能的服务器,来处理专门的业务。比如一个大型应用中,地图功能需要配置一台地图服务器,实况功能需要配置一台媒体服务器,数据上传专门来一台服务器,数据库系统再来一台,管理这些服务器还需要一台。在一些高并发的地方,一台服务器处理不过来,要把请求分散到各个服务器上进行处理。
服务器越来越多,不可避免地要考虑一个问题,跨域请求,跨域本身是一种安全策略,为了网络安全,不能直接请求非本服务器上的资源,甚至连本服务器上不同的端口,也不能直接访问。
为了能正常请求到我们想要的东西,那我们就必须找一个代理,来帮我们达到目的。这就是今天的代理模式。
一、代理模式
解决跨域时我们可以用一些小技巧,比如 a 标签的 src 属性,在跳转的时候就可以向其他服务器发送请求。这个请求只能是 get 。并且是单向的,没有响应数据。
//统计代理
var Count = (function() {
var _img = new Image();
//返回统计函数
return function(param) {
//统计请求字符串
var str = 'http://www.count.com/a.gif?';
//拼接字符串
for(var i in param) {
str += i + '=' + param[i];
}
//发送统计请求
_img.src = str;
}
})();
Count({num: 10});
上面方法毕竟受限,我们还有一种通过 script 标签来完成代理, JSONP 方案
//前端浏览器页面
<script type="text/JavaScript">
//回调函数,打印出来请求数据与响应数据
function jsonpCallBAck(res, req) {
console.log(res, req);
}
</script>
<script
type="text/JavaScript"
src="http://localhost/test/jsonp/php?callback=jsonp CallBack&data=getJsonPData">
</script>
//另外一个域下服务器请求接口
<?php
/*后端获取请求字段,并生成返回内容*/
$data = $_GET["data"];
$callback = $_GET["callback"];
echo $callback."('success', '".$data."')";
?>
这种方式类似河上有一条小船,你把要发送的消息放到小船上,让小船带到对岸,然后对岸的人将数据放到船里带回来。这种方式需要其他域有一定的可靠性,因为不知道船会带回来什么。
还有一种方式,应用的比较广泛,比如现在页面的请求在域X中,请求的地址为域 Y ,那么这两个不同的域会出现跨域问题,那么如果我现在 X 域中,中专门配置一个服务器 A 做代理,所以 X 的请求,先发送到 A 上,然后服务器之间通信是没有跨域问题的,就可以让 A 代理转发请求。
现代常用的代理有 Nginx , GO 等,大家感兴趣可以去看看,据大神说, GO 代理一个周末就能搞定。
二、装饰着模式
前面的文章我们已经介绍过装饰者模式了,这里又拿出来,是为了给大家解释一个问题。我们先复习下,装饰者模式是可以不改变原有功能的情况下,增加一些新的功能。之前我们举的例子是在 Function 的原型上增加一个 before 和 after 函数,让执行 函数的时候先执行 before 再执行 fucntion 最后再执行 after ,通过这种方法实现的装饰者。
下面我们用一种新的实现形式,比如这里有个输入框,点击可以显示输入规则,然后来了个需求,不光要显示输入规则,还希望同时让输入框变为红色。
修改很简单,找到对应的输入框,增加一个输入框颜色修改就可以了,但是我一个一面如果有很多输入框,都需要增加这个功能,这个时候一个一个修改,就很耗时耗力了。我们可以使用装饰者模式。
//装饰者模式
var decorator = function(input, fn) {
//获取事件源
var input = document.getElementById(input);
//若事件源已经绑定事件
if(typeof input.onclick === 'function') {
//缓存事件原有回调函数
var oldClickFn = input.onclick;
//为事件定义新的事件
input.onclick = function() {
//事件原有回调函数
oldClickFn();
//新增回调函数
fn();
}
} else {
//直接为事件添加新增回调
input.onclick = fn;
}
//其他事情
}
使用的时候,只要把需要添加的输入框执行一下 decorator 函数就可以。
decorator('tel_input', function() {
document.getElementById('tel_demo_text').style.border = '1px solid red';
});
为什么要重新举一个装饰者模式的例子,主要是想让大家明白,设计模式是一种思想,这种思想不受代码限制,不是说我的代码必须要写的一摸一样,才算使用了设计模式,它是一种思维方式。正如本系列文章开篇提到的,设计模式是兵法,要学会兵法的精髓,按照兵法去思考而不是按照兵法做。
之前介绍的那么多种设计模式里面,除了抽象工厂方法必须要用继承来实现,其余的每一种方法,都不是只有例子里的用法。可以展开自己的想象,很多时候你可能没注意,你已经在使用设计模式的思想来解决问题了。
三、桥接模式
接下来介绍一个大家一定用过,只是当时不知道的设计模式。桥接模式,举个例子,有一行导航栏,要求鼠标悬浮的时候背景色变深,旁边的导航栏,要求鼠标经过的时候里面的字体颜色。功能差不多,但是又不一样,感觉能合到一起,但是又不知道怎么整合。
var spans = document.getElementByTagName('span');
spans[0].onmouseover = function() {
this.style.color = 'red';
this.style.background = '#ddd';
};
spans[0].onmouseout = function() {
this.style.color = '#333';
this.style.background = '#f5f5f5';
};
spans[1].onmouseover = function() {
this.getElementByTagName('strong')[0].style.color = 'red';
this.getElementByTagName('strong')[0].style.background = '#ddd';
};
spans[1].onmouseout = function() {
this.getElementByTagName('strong')[0].style.color = '#333';
this.getElementByTagName('strong')[0].style.background = '#f5f5f5';
};
上面代码可以看出来,是有一部分冗余的,我们需要对事件的回调函数再处理一下。具体做法如下,我们需要创建一个函数,解除与事件的 this 耦合。
//抽象
function changeColor(dom, color, bg) {
//设置元素字体
dom.style.color = color;
//设置元素的背景颜色
dom.style.background = bg;
}
var spans = document.getElementByTagName('span');
spans[0].onmouseover = function() {
changeColor(this, 'red', '#ddd');
};
spans[0].onmouseout = function() {
changeColor(this, '#333', '#f5f5f5');
};
spans[1].onmouseover = function() {
changeColor(this.getElementByTagName('strong')[0], 'red', '#ddd');
};
spans[1].onmouseout = function() {
changeColor(this.getElementByTagName('strong')[0], '#333', '#f5f5f5');
};
上面的桥接模式用起来,有没有一点像我们之前学习的适配器模式,两种模式总体上是有一点相似,毕竟桥接与适配器,本身中文意思就有相似之处,只不过适配器模式多用在接口转换上,而桥接的特点是实现层与抽象层的分离。
重申一下,设计模式是思想,要学会按照设计模式思考问题,不要受上面样例的束缚。