[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 );
 }
[js高手之路]设计模式系列课程-发布者,订阅者重构购物车的更多相关文章
- [js高手之路] 设计模式系列课程 - jQuery的extend插件机制
		
这里在之前的文章[js高手之路] 设计模式系列课程 - jQuery的链式调用与灵活的构造函数基础上增加一个extend浅拷贝,可以为对象方便的扩展属性和方法, jquery的插件扩展机制,大致就是这 ...
 - [js高手之路]设计模式系列课程-组合模式+寄生组合继承实战新闻列表
		
所谓组合模式,就是把一堆结构分解出来,组成在一起,现实中很多这样的例子,如: 1.肯德基套餐就是一种组合模式, 比如鸡腿堡套餐,一般是是由一个鸡腿堡,一包薯条,一杯可乐等组成的 2.组装的台式机同理, ...
 - [js高手之路] 设计模式系列课程 - jQuery的链式调用与灵活的构造函数
		
一.我们从一个简单的构造函数+原型程序开始 var G = function(){}; G.prototype = { length : 5, size : function(){ return th ...
 - [js高手之路]设计模式系列课程-单例模式实现模态框
		
什么是单例呢? 单,就是一个的意思.例:就是实例化出来的对象,那合在一起就是保证一个构造函数只能new出一个实例,为什么要学习单例模式呢?或者说单例模式有哪些常见的应用场景.它的使用还是很广泛,比如: ...
 - [js高手之路]设计模式系列课程-设计一个模块化扩展功能(define)和使用(use)库
		
模块化的诞生标志着javascript开发进入工业时代,近几年随着es6, require js( sea js ), node js崛起,特别是es6和node js自带模块加载功能,给大型程序开发 ...
 - [js高手之路]设计模式系列课程-委托模式实战微博发布功能
		
在实际开发中,经常需要为Dom元素绑定事件,如果页面上有4个li元素,点击对应的li,弹出对应的li内容,怎么做呢?是不是很简单? 大多数人的做法都是:获取元素,绑定事件 <ul> < ...
 - [js高手之路] 设计模式系列课程 - DOM迭代器(2)
		
如果你对jquery比较熟悉的话,应该用过 eq, first, last, get, prev, next, siblings等过滤器和方法.本文,我们就用迭代设计模式来封装实现,类似的功能 < ...
 - [js高手之路] 设计模式系列课程 - 迭代器(1)
		
迭代器是指通过一种形式依次遍历数组,对象,或者类数组结构中的每个元素. 常见的有jquery中的each方法, ES5自带的forEach方法. 下面我们就来自定义一个类似jquery或者ES5的迭代 ...
 - [js高手之路] es6系列教程 - 迭代器与生成器详解
		
什么是迭代器? 迭代器是一种特殊对象,这种对象具有以下特点: 1,所有对象都有一个next方法 2,每次调用next方法,都会返回一个对象,该对象包含两个属性,一个是value, 表示下一个将要返回的 ...
 
随机推荐
- 使用curl模拟ip和来源进行网站采集的实现方法
			
对于限制了ip和来源的网站,使用正常的采集方式是不行的.本文将介绍一种方法,使用php的curl类实现模拟ip和来源,实现采集限制ip和来源的网站. 1.设置页面限制ip和来源访问 server.ph ...
 - 使用Dubbo、JSF等RPC框架时,对于异常的处理
			
无论是Dubbo还是JSF等RPC框架,一般都会把接口分为2部分: 1,服务端(provider) 2,客户端(consumer) 由于,客户端与服务端可能不在同一个应用中,所以客户端一般在调用服务端 ...
 - 《Unity3D-播放被打中的时候粒子的特效的代码》
			
//思路:首先我们需要给这个敌人身上放置上被打中的时候的粒子效果的组件,然后在获取和初始化这个组件然后在播放这个组件.虽然这个过程很简单但是我们要让 组件随着敌人的移动的时候随时触发就必须将这个组件的 ...
 - 花了一年时间开发的TTF2FNT字库转换软件
			
TTF(True Type Font)字库是微软定义的基于windows的标准字库格式.但其由于专利保护以及无法跨平台导致TTF字库在实际应用中无法有效使用. 为此我开发了TTF2FNT字库转换软件, ...
 - jdk 环境变量配置方法总结
			
全部修改在用户变量/系统变量 系统变量→新建 JAVA_HOME 变量 .变量值填写jdk的安装目录(本人是 D:\java\Java\jdk1.8.0_72) 系统变量→寻找 Path 变量→编辑在 ...
 - WinFrom通过委托传递事件
			
今天一个功能需要动态创建pictruebox然后根据时间来倒叙显示,一开始对于这个需求摸不着头脑,后来在公司的大神帮助下实现了,话不多说具体实现上代码了: 1.动态添加控件并倒叙 首先添加一个用户控件 ...
 - SSE再学习:灵活运用SIMD指令6倍提升Sobel边缘检测的速度(4000*3000的24位图像时间由180ms降低到30ms)。
			
这半年多时间,基本都在折腾一些基本的优化,有很多都是十几年前的技术了,从随大流的角度来考虑,研究这些东西在很多人看来是浪费时间了,即不能赚钱,也对工作能力提升无啥帮助.可我觉得人类所谓的幸福,可以分为 ...
 - Android文件上传与下载
			
文件上传与下载 文件上传 -- 服务端 以Tomcat为服务器,Android客服端访问Servlet,经Servlet处理逻辑,最终将文件上传,这里就是简单模拟该功能,就将文件上传到本机的D:\\u ...
 - zookeeper-3.4.5安装&3台机器安装之后 ./zkServer.sh status 之后会显示“Error contacting service. It is probably not running.”的解决办法
			
安装文件上传工具:yum install lrzsz成功安装后有如下的提示:Complete![root@server01 apps]# yum install lrzszLoaded plugins ...
 - Samba服务部署
			
Samba,是种用来让UNIX系列的操作系统与微软Windows操作系统的SMB/CIFS(Server Message Block/Common Internet File System)网络协议做 ...