发布者订阅者模式,是一种很常见的模式,比如:
一、买卖房子
生活中的买房,卖房,中介就构成了一个发布订阅者模式,买房的人,一般需要的是房源,价格,使用面积等信息,他充当了订阅者的角色
中介拿到卖主的房源信息,根据手头上掌握的客户联系信息(买房的人的手机号),通知买房的人,他充当了发布者的角色
卖主想卖掉自己的房子,就需要告诉中介,把信息交给中介发布
二,网站订阅信息的用户
订阅者角色:需要订阅某类信息的网民,如某个网站的javascript类型文章
发布者角色:邮箱服务器,根据网站收集到的用户订阅邮箱,通知用户.
网站主想把信息告诉订阅者,需要把文章相关内容告诉邮箱服务器去发送
等等非常多的例子,不一一列举
本文用网站订阅的方式,推导发布者-订阅者框架,然后用发布者-订阅者框架来重构一个简单的购物车
1 var Site = {};
2 Site.userList = [];
3 Site.subscribe = function( fn ){
4 this.userList.push( fn );
5 }
6 Site.publish = function(){
7 for( var i = 0, len = this.userList.length; i < len; i++ ){
8 this.userList[i].apply( this, arguments );
9 }
10 }
11 Site.subscribe( function( type ){
12 console.log( "网站发布了" + type + "内容" );
13 });
14 Site.subscribe( function( type ){
15 console.log( "网站发布了" + type + "内容" );
16 });
17 Site.publish( 'javascript' );
18 Site.publish( 'html5' );
Site.userList就是用来保存订阅者
Site.subscribe就是具体的订阅者,把每一个订阅者订阅的具体信息保存在Site.userList
Site.publish就是发布者:根据保存的userList,一个个遍历(通知),执行里面的业务逻辑
但是这个,发布订阅者模式,有个问题,不能订阅想要的类型,上例我加了2个订阅者(第11行,第14行),只要网站发了信息,全部能收到,但是有些用户可能只想收到javascript或者html5的,所以,接下来,我们需要继续完善,希望能够接收到具体的信息,不是某人订阅的类型,就不接收
1 var Site = {};
2 Site.userList = {};
3 Site.subscribe = function (key, fn) {
4 if (!this.userList[key]) {
5 this.userList[key] = [];
6 }
7 this.userList[key].push(fn);
8 }
9 Site.publish = function () {
10 var key = Array.prototype.shift.apply(arguments),
11 fns = this.userList[key];
12 if ( !fns || fns.length === 0) {
13 console.log( '没有人订阅' + key + "这个分类的文章" );
14 return false;
15 }
16 for (var i = 0, len = fns.length; i < len; i++) {
17 fns[i].apply(this, arguments);
18 }
19 }
20
21 Site.subscribe( "javascript", function( title ){
22 console.log( title );
23 });
24
25 Site.subscribe( "es6", function( title ){
26 console.log( title );
27 });
28
29 Site.publish( "javascript", "[js高手之路]寄生组合式继承的优势" );
30 Site.publish( "es6", "[js高手之路]es6系列教程 - var, let, const详解" );
31 Site.publish( "html5", "html5新的语义化标签" );
输出结果:
[js高手之路]寄生组合式继承的优势
[js高手之路]es6系列教程 - var, let, const详解
没有人订阅html5这个分类的文章
我们可以看到,只有订阅了javascript类型文章的人,才能收到 ”寄生组合式继承的优势” 这篇文章,发布html5类型的时候,没有任何人会收到.
es6类型的,只有订阅es6的人,才能收到
我们已经有了一个基本的发布订阅者框架,接下来,把他完善成一个框架,便于其他功能或者其他网站系统的相同功能可以重用他
var Event = {
userList : {},
subscribe : function (key, fn) {
if (!this.userList[key]) {
this.userList[key] = [];
}
this.userList[key].push(fn);
},
publish : function () {
var key = Array.prototype.shift.apply(arguments),
fns = this.userList[key];
if (!fns || fns.length === 0) {
console.log('没有人订阅' + key + "这个分类的文章");
return false;
}
for (var i = 0, len = fns.length; i < len; i++) {
fns[i].apply(this, arguments);
}
}
};
var extend = function( dstObj, srcObj ){
for( var key in srcObj ){
dstObj[key] = srcObj[key];
}
}
var Site = {};
extend( Site, Event );
Site.subscribe( "javascript", function( title ){
console.log( title );
});
Site.subscribe( "es6", function( title ){
console.log( title );
});
Site.publish( "javascript", "寄生组合式继承的优势" );
Site.publish( "es6", "es6系列教程 - var, let, const详解" );
Site.publish( "html5", "html5新的语义化标签" );
然后,我们来重构一个购物车实例,没有重构之前,我的购物车用的是面向过程:
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Title</title>
6 <script src="js/cart.js"></script>
7 </head>
8 <body>
9 <div id="box">
10 <ul>
11 <li>
12 <input type="button" value="-">
13 <span class="num">0</span>
14 <input type="button" value="+">
15 <span>单价:</span>
16 <span class="unit">15元;</span>
17 <span class="label">小计:</span>
18 <span class="subtotal">0</span>元
19 </li>
20 <li>
21 <input type="button" value="-">
22 <span class="num">0</span>
23 <input type="button" value="+">
24 <span>单价:</span>
25 <span class="unit">10元;</span>
26 <span class="label">小计:</span>
27 <span class="subtotal">0</span>元
28 </li>
29 <li>
30 <input type="button" value="-">
31 <span class="num">0</span>
32 <input type="button" value="+">
33 <span>单价:</span>
34 <span class="unit">5元;</span>
35 <span class="label">小计:</span>
36 <span class="subtotal">0</span>元
37 </li>
38 <li>
39 <input type="button" value="-">
40 <span class="num">0</span>
41 <input type="button" value="+">
42 <span>单价:</span>
43 <span class="unit">2元;</span>
44 <span class="label">小计:</span>
45 <span class="subtotal">0</span>元
46 </li>
47 <li>
48 <input type="button" value="-">
49 <span class="num">0</span>
50 <input type="button" value="+">
51 <span>单价:</span>
52 <span class="unit">1元;</span>
53 <span class="label">小计:</span>
54 <span class="subtotal">0</span>元
55 </li>
56 </ul>
57 <div class="total-box">
58 商品一共
59 <span id="goods-num">0</span>
60 件;
61 一共花费
62 <span id="total-price">0</span>
63 元;
64 其中最贵的商品单价是<span id="unit-price">0</span>元
65 </div>
66 </div>
67 </body>
68 </html>
cart.js文件:
1 function getByClass(cName, obj) {
2 var o = null;
3 if (arguments.length == 2) {
4 o = obj;
5 } else {
6 o = document;
7 }
8 var allNode = o.getElementsByTagName("*");
9 var aNode = [];
10 for( var i = 0 ; i < allNode.length; i++ ){
11 if( allNode[i].className == cName ){
12 aNode.push( allNode[i] );
13 }
14 }
15 return aNode;
16 }
17
18 function getSubTotal( unitPrice, goodsNum ){
19 return unitPrice * goodsNum;
20 }
21
22 function getSum(){ //计算总花费
23 var aSubtotal = getByClass("subtotal");
24 var res = 0;
25 for( var i = 0; i < aSubtotal.length; i++ ){
26 res += parseInt(aSubtotal[i].innerHTML);
27 }
28 return res;
29 }
30
31 function compareUnit() { //比单价,找出最高的单价
32 var aNum = getByClass( "num");
33 var aUnit = getByClass( "unit");
34 var temp = 0;
35 for( var i = 0; i < aNum.length; i++ ){
36 if( parseInt(aNum[i].innerHTML) != 0 ){
37 if( temp < parseInt(aUnit[i].innerHTML) ) {
38 temp = parseInt(aUnit[i].innerHTML);
39 }
40 }
41 }
42 return temp;
43 }
44
45 window.onload = function () {
46 var aInput = document.getElementsByTagName("input");
47 var total = 0;
48 var oGoodsNum = document.getElementById("goods-num");
49 var oTotalPrice = document.getElementById("total-price");
50 var oUnitPrice = document.getElementById("unit-price");
51
52 for (var i = 0; i < aInput.length; i++) {
53 if (i % 2 != 0) { //加号
54 aInput[i].onclick = function () {
55 //当前加号所在行的数量
56 var aNum = getByClass( "num", this.parentNode );
57 var n = parseInt( aNum[0].innerHTML );
58 n++;
59 aNum[0].innerHTML = n;
60 //获取单价
61 var aUnit = getByClass( "unit", this.parentNode );
62 var unitPrice = parseInt(aUnit[0].innerHTML);
63 var subtotal = getSubTotal( unitPrice, n );
64 var aSubtotal = getByClass( "subtotal", this.parentNode );
65 aSubtotal[0].innerHTML = subtotal;
66 total++; //商品总数
67 oGoodsNum.innerHTML = total;
68 oTotalPrice.innerHTML = getSum();
69 oUnitPrice.innerHTML = compareUnit();
70 }
71 }else {
72 aInput[i].onclick = function(){
73 var aNum = getByClass( "num", this.parentNode );
74 if ( parseInt( aNum[0].innerHTML ) != 0 ){
75 var n = parseInt( aNum[0].innerHTML );
76 n--;
77 aNum[0].innerHTML = n;
78 //获取单价
79 var aUnit = getByClass( "unit", this.parentNode );
80 var unitPrice = parseInt(aUnit[0].innerHTML);
81 var subtotal = getSubTotal( unitPrice, n );
82 var aSubtotal = getByClass( "subtotal", this.parentNode );
83 aSubtotal[0].innerHTML = subtotal;
84 total--; //商品总数
85 oGoodsNum.innerHTML = total;
86 oTotalPrice.innerHTML = getSum();
87 oUnitPrice.innerHTML = compareUnit();
88 }
89 }
90 }
91 }
92 }
耦合度太高,可维护性很差.
重构之后的购物车:
1 window.onload = function () {
2 var Event = {
3 userList: {},
4 subscribe: function (key, fn) {
5 if (!this.userList[key]) {
6 this.userList[key] = [];
7 }
8 this.userList[key].push(fn);
9 },
10 publish: function () {
11 var key = Array.prototype.shift.apply(arguments),
12 fns = this.userList[key];
13 if (!fns || fns.length === 0) {
14 return false;
15 }
16 for (var i = 0, len = fns.length; i < len; i++) {
17 fns[i].apply(this, arguments);
18 }
19 }
20 };
21 (function(){
22 var aBtnMinus = document.querySelectorAll( "#box li>input:first-child"),
23 aBtnPlus = document.querySelectorAll( "#box li>input:nth-of-type(2)"),
24 curNum = 0, curUnitPrice = 0;
25
26 for( var i = 0, len = aBtnMinus.length; i < len; i++ ){
27 aBtnMinus[i].index = aBtnPlus[i].index = i;
28 aBtnMinus[i].onclick = function(){
29 (this.parentNode.children[1].innerHTML > 0) && Event.publish( "total-goods-num-minus" );
30 --this.parentNode.children[1].innerHTML < 0 && (this.parentNode.children[1].innerHTML = 0);
31 curUnitPrice = this.parentNode.children[4].innerHTML;
32 Event.publish( "minus-num" + this.index,
33 parseInt( curUnitPrice ),
34 parseInt( this.parentNode.children[1].innerHTML )
35 );
36 };
37 aBtnPlus[i].onclick = function(){
38 (this.parentNode.children[1].innerHTML >= 0) && Event.publish( "total-goods-num-plus" );
39 this.parentNode.children[1].innerHTML++;
40 curUnitPrice = this.parentNode.children[4].innerHTML;
41 Event.publish( "plus-num" + this.index,
42 parseInt( curUnitPrice ),
43 parseInt( this.parentNode.children[1].innerHTML )
44 );
45 }
46 }
47 })();
48 (function(){
49 var aSubtotal = document.querySelectorAll("#box .subtotal"),
50 oGoodsNum = document.querySelector("#goods-num"),
51 oTotalPrice = document.querySelector("#total-price");
52 Event.subscribe( 'total-goods-num-plus', function(){
53 ++oGoodsNum.innerHTML;
54 });
55 Event.subscribe( 'total-goods-num-minus', function(){
56 --oGoodsNum.innerHTML;
57 });
58 for( let i = 0, len = aSubtotal.length; i < len; i++ ){
59 Event.subscribe( 'minus-num' + i, function( unitPrice, num ){
60 aSubtotal[i].innerHTML = unitPrice * num;
61 });
62 Event.subscribe( 'plus-num' + i, function( unitPrice, num ){
63 aSubtotal[i].innerHTML = unitPrice * num;
64 });
65 }
66 })();
67 console.log( Event.userList );
68 }