[js高手之路]設計模式系列課程-發布者,訂閱者重構購物車
發布者訂閱者模式,是一種很常見的模式,比如:
一、買賣房子
生活中的買房,賣房,中介就構成了一個發布訂閱者模式,買房的人,一般需要的是房源,價格,使用面積等信息,他充當了訂閱者的角色
中介拿到賣主的房源信息,根據手頭上掌握的客戶聯系信息(買房的人的手機號),通知買房的人,他充當了發布者的角色
賣主想賣掉自己的房子,就需要告訴中介,把信息交給中介發布
二,網站訂閱信息的用戶
訂閱者角色:需要訂閱某類信息的網民,如某個網站的javascript類型文章
發布者角色:郵箱服務器,根據網站收集到的用戶訂閱郵箱,通知用戶.
網站主想把信息告訴訂閱者,需要把文章相關內容告訴郵箱服務器去發送
等等非常多的例子,不一一列舉
本文用網站訂閱的方式,推導發布者-訂閱者框架,然後用發布者-訂閱者框架來重構一個簡單的購物車
var Site = {}; Site.userList = []; Site.subscribe = function( fn ){ this.userList.push( fn ); } Site.publish = function(){ for( var i = 0, len = this.userList.length; i < len; i++ ){ this.userList[i].apply( this, arguments ); } } Site.subscribe( function( type ){ console.log( "網站發布了" + type + "內容" ); }); Site.subscribe( function( type ){ console.log( "網站發布了" + type + "內容" ); }); Site.publish( ‘javascript‘ ); Site.publish( ‘html5‘ );
Site.userList就是用來保存訂閱者
Site.subscribe就是具體的訂閱者,把每一個訂閱者訂閱的具體信息保存在Site.userList
Site.publish就是發布者:根據保存的userList,一個個遍歷(通知),執行裏面的業務邏輯
但是這個,發布訂閱者模式,有個問題,不能訂閱想要的類型,上例我加了2個訂閱者(第11行,第14行),只要網站發了信息,全部能收到,但是有些用戶可能只想收到javascript或者html5的,所以,接下來,我們需要繼續完善,希望能夠接收到具體的信息,不是某人訂閱的類型,就不接收
var Site = {}; Site.userList = {}; Site.subscribe = function (key, fn) { if (!this.userList[key]) { this.userList[key] = []; } this.userList[key].push(fn); } Site.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); } } Site.subscribe( "javascript", function( title ){ console.log( title ); }); Site.subscribe( "es6", function( title ){ console.log( title ); }); Site.publish( "javascript", "[js高手之路]寄生組合式繼承的優勢" ); Site.publish( "es6", "[js高手之路]es6系列教程 - var, let, const詳解" ); 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新的語義化標簽" );
然後,我們來重構一個購物車實例,沒有重構之前,我的購物車用的是面向過程:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="js/cart.js"></script> </head> <body> <div id="box"> <ul> <li> <input type="button" value="-"> <span class="num">0</span> <input type="button" value="+"> <span>單價:</span> <span class="unit">15元;</span> <span class="label">小計:</span> <span class="subtotal">0</span>元 </li> <li> <input type="button" value="-"> <span class="num">0</span> <input type="button" value="+"> <span>單價:</span> <span class="unit">10元;</span> <span class="label">小計:</span> <span class="subtotal">0</span>元 </li> <li> <input type="button" value="-"> <span class="num">0</span> <input type="button" value="+"> <span>單價:</span> <span class="unit">5元;</span> <span class="label">小計:</span> <span class="subtotal">0</span>元 </li> <li> <input type="button" value="-"> <span class="num">0</span> <input type="button" value="+"> <span>單價:</span> <span class="unit">2元;</span> <span class="label">小計:</span> <span class="subtotal">0</span>元 </li> <li> <input type="button" value="-"> <span class="num">0</span> <input type="button" value="+"> <span>單價:</span> <span class="unit">1元;</span> <span class="label">小計:</span> <span class="subtotal">0</span>元 </li> </ul> <div class="total-box"> 商品一共 <span id="goods-num">0</span> 件; 一共花費 <span id="total-price">0</span> 元; 其中最貴的商品單價是<span id="unit-price">0</span>元 </div> </div> </body> </html>
cart.js文件:
function getByClass(cName, obj) { var o = null; if (arguments.length == 2) { o = obj; } else { o = document; } var allNode = o.getElementsByTagName("*"); var aNode = []; for( var i = 0 ; i < allNode.length; i++ ){ if( allNode[i].className == cName ){ aNode.push( allNode[i] ); } } return aNode; } function getSubTotal( unitPrice, goodsNum ){ return unitPrice * goodsNum; } function getSum(){ //計算總花費 var aSubtotal = getByClass("subtotal"); var res = 0; for( var i = 0; i < aSubtotal.length; i++ ){ res += parseInt(aSubtotal[i].innerHTML); } return res; } function compareUnit() { //比單價,找出最高的單價 var aNum = getByClass( "num"); var aUnit = getByClass( "unit"); var temp = 0; for( var i = 0; i < aNum.length; i++ ){ if( parseInt(aNum[i].innerHTML) != 0 ){ if( temp < parseInt(aUnit[i].innerHTML) ) { temp = parseInt(aUnit[i].innerHTML); } } } return temp; } window.onload = function () { var aInput = document.getElementsByTagName("input"); var total = 0; var oGoodsNum = document.getElementById("goods-num"); var oTotalPrice = document.getElementById("total-price"); var oUnitPrice = document.getElementById("unit-price"); for (var i = 0; i < aInput.length; i++) { if (i % 2 != 0) { //加號 aInput[i].onclick = function () { //當前加號所在行的數量 var aNum = getByClass( "num", this.parentNode ); var n = parseInt( aNum[0].innerHTML ); n++; aNum[0].innerHTML = n; //獲取單價 var aUnit = getByClass( "unit", this.parentNode ); var unitPrice = parseInt(aUnit[0].innerHTML); var subtotal = getSubTotal( unitPrice, n ); var aSubtotal = getByClass( "subtotal", this.parentNode ); aSubtotal[0].innerHTML = subtotal; total++; //商品總數 oGoodsNum.innerHTML = total; oTotalPrice.innerHTML = getSum(); oUnitPrice.innerHTML = compareUnit(); } }else { aInput[i].onclick = function(){ var aNum = getByClass( "num", this.parentNode ); if ( parseInt( aNum[0].innerHTML ) != 0 ){ var n = parseInt( aNum[0].innerHTML ); n--; aNum[0].innerHTML = n; //獲取單價 var aUnit = getByClass( "unit", this.parentNode ); var unitPrice = parseInt(aUnit[0].innerHTML); var subtotal = getSubTotal( unitPrice, n ); var aSubtotal = getByClass( "subtotal", this.parentNode ); aSubtotal[0].innerHTML = subtotal; total--; //商品總數 oGoodsNum.innerHTML = total; oTotalPrice.innerHTML = getSum(); oUnitPrice.innerHTML = compareUnit(); } } } } }
耦合度太高,可維護性很差.
重構之後的購物車:
window.onload = function () { 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) { return false; } for (var i = 0, len = fns.length; i < len; i++) { fns[i].apply(this, arguments); } } }; (function(){ var aBtnMinus = document.querySelectorAll( "#box li>input:first-child"), aBtnPlus = document.querySelectorAll( "#box li>input:nth-of-type(2)"), curNum = 0, curUnitPrice = 0; for( var i = 0, len = aBtnMinus.length; i < len; i++ ){ aBtnMinus[i].index = aBtnPlus[i].index = i; aBtnMinus[i].onclick = function(){ (this.parentNode.children[1].innerHTML > 0) && Event.publish( "total-goods-num-minus" ); --this.parentNode.children[1].innerHTML < 0 && (this.parentNode.children[1].innerHTML = 0); curUnitPrice = this.parentNode.children[4].innerHTML; Event.publish( "minus-num" + this.index, parseInt( curUnitPrice ), parseInt( this.parentNode.children[1].innerHTML ) ); }; aBtnPlus[i].onclick = function(){ (this.parentNode.children[1].innerHTML >= 0) && Event.publish( "total-goods-num-plus" ); this.parentNode.children[1].innerHTML++; curUnitPrice = this.parentNode.children[4].innerHTML; Event.publish( "plus-num" + this.index, parseInt( curUnitPrice ), parseInt( this.parentNode.children[1].innerHTML ) ); } } })(); (function(){ var aSubtotal = document.querySelectorAll("#box .subtotal"), oGoodsNum = document.querySelector("#goods-num"), oTotalPrice = document.querySelector("#total-price"); Event.subscribe( ‘total-goods-num-plus‘, function(){ ++oGoodsNum.innerHTML; }); Event.subscribe( ‘total-goods-num-minus‘, function(){ --oGoodsNum.innerHTML; }); for( let i = 0, len = aSubtotal.length; i < len; i++ ){ Event.subscribe( ‘minus-num‘ + i, function( unitPrice, num ){ aSubtotal[i].innerHTML = unitPrice * num; }); Event.subscribe( ‘plus-num‘ + i, function( unitPrice, num ){ aSubtotal[i].innerHTML = unitPrice * num; }); } })(); console.log( Event.userList ); }
本文出自 “ghostwu” 博客,請務必保留此出處http://ghostwu.blog.51cto.com/11192807/1962432
[js高手之路]設計模式系列課程-發布者,訂閱者重構購物車