用生命谱写代码的赞歌

0%

JavaScript设计模式实践之代理模式

下面代码是图片预加载的一个简单实现, 先不考虑加载图片时 onErroronAbort , 超时等问题。

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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
</head>

<body>
<button id='btnLoadImg'>加载图片</button>
<br>
<div id='imgContainer'>
</div>
<br>

<script type='text/javascript' src="../script/jquery-1.9.1.js"></script>
<script type='text/javascript'>
$(document).ready(function () {
$('#btnLoadImg').bind('click', doLoadImg);
});

function doLoadImg() {
var eleImg = createImgElement();
document.getElementById('imgContainer').appendChild(eleImg);

loadImg(eleImg, 'https://i.postimg.cc/tRMNsqnJ/Yukee4.png');
}

//创建img标签
//这里用自执行函数加一个闭包,是为了可以创建多个id不同的img标签。
var createImgElement = (function () {
var index = 0;

return function () {
var eleImg = document.createElement('img');
eleImg.setAttribute('width', '200');
eleImg.setAttribute('heght', '150');
eleImg.setAttribute('id', 'img' + index++);
return eleImg;
};
})();

//预加载图片
//给img标签设一个加载图片,通过Image对象预先加载实际图片加载完成后设到img标签上
function loadImg(img, src) {
var imgCache = new Image();
imgCache.onload = function(){
img.src = this.src;
};

img.src = 'loading.gif';
imgCache.src = src;
}
//上述函数调整后可使用代理函数进行加载
//如果某一天不需要预加载了,就把loadImgProxy换成loadImg即可
</script>
</body>

</html>

上述代码在功能上实现了图片预加载,但是它包含了预加载和加载两项职责,违反了 “单一职责原则”。所谓的职责就是“会发生的变化”,如果网速不再是问题或者加载图片的分辨率被控制在很小的时候等,需要去掉预加载的功能,这时候就要修改loadImg的代码,就要重新跑所有相关的测试,即违反了 “开闭原则”,又增加测试工作。

设计模式有如下原则:

  1. 开闭原则:对扩展开放,对修改关闭
  2. 里氏转换原则:子类继承父类,单独调用完全可以运行
  3. 依赖倒转原则:引用一个对象,如果这个对象有底层类型,直接引用底层
  4. 接口隔离原则:每个接口应该是一个角色
  5. 合成/聚合复用原则:新对象应该使用一些已有的对象,使之成为新对象的一部分
  6. 迪米特原则:一个对象应该对其它对象有尽可能少的了解

加载和预加载其实就是代理模式的一种,代理模式可以理解为你想给MM送东西,但是不知道MM喜欢什么,就找她的闺蜜帮你买她喜欢的东西交给你,然后你再送给她。

将预加载功能改为代理模式可以理解为:本体先显示个门面,让代理去帮忙加载大图,加载完了,告诉本体,本体直接把图片贴上去就行了

  • 加载本体函数

    1
    2
    3
    function loadImg(img, src) {
    img.src = src;
    }
  • 写一个预加载代理函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function loadImgProxy(img, src){
    var imgCache = new Image();
    imgCache.onload = function(){
    loadImg(img, this.src);
    };

    loadImg(img, 'loading.gif');
    imgCache.src = src;
    }

在代理函数中,先让本体加载 loading.gif,等大图加载完了再让本体加载实际图片。代理函数与本体函数接口参数一致,职责分的很清楚,如果将来需要去掉预加载也不需要重新修改本体和代理的代码,只需要在调用的地方把代理函数名字换成本体函数名字即可。

将上面的代理函数修改一下,让其适应加载多个图片的场景,其实就是做一个闭包,将缓存image对象变为私有,不必每次都new一个新的image对象,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
var loadImgProxy = (function(){
var imgCache = new Image();

return function(img, src){
imgCache.onload = function(){
loadImg(img, this.src);
};

loadImg(img, 'loading.gif');
imgCache.src = src;
};
})();

小结

  1. 通过代理对象,添加了新的行为,符合开放-封闭原则
  2. 图片预加载和给 img 设置 src 这两个功能被分隔到两个方法中,它们各自变化不影响另外一个
  3. 如果以后不需要预加载了,只需要修改函数名即可

参考文章