[js高手之路]设计模式系列课程-发布者,订阅者重构购物车的实例


Posted in Javascript onAugust 29, 2017

发布者订阅者模式,是一种很常见的模式,比如:

一、买卖房子

生活中的买房,卖房,中介就构成了一个发布订阅者模式,买房的人,一般需要的是房源,价格,使用面积等信息,他充当了订阅者的角色

中介拿到卖主的房源信息,根据手头上掌握的客户联系信息(买房的人的手机号),通知买房的人,他充当了发布者的角色

卖主想卖掉自己的房子,就需要告诉中介,把信息交给中介发布

二,网站订阅信息的用户

订阅者角色:需要订阅某类信息的网民,如某个网站的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高手之路]设计模式系列课程-发布者,订阅者重构购物车的实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
从javascript语言本身谈项目实战
Dec 27 Javascript
javascript 放大镜效果js组件 qsoft.PopBigImage.v0.35 加入了chrome支持
Apr 07 Javascript
也说JavaScript中String类的replace函数
Sep 22 Javascript
jQuery EasyUI API 中文文档 - ProgressBar 进度条
Sep 29 Javascript
详解AngularJS的通信机制
Jun 18 Javascript
15位和18位身份证JS校验的简单实例
Jul 18 Javascript
jQuery EasyUI中的日期控件DateBox修改方法
Nov 09 Javascript
jQuery实现移动端Tab选项卡效果
Mar 15 Javascript
Angular中封装fancyBox(图片预览)遇到问题小结
Sep 01 Javascript
微信小程序 动态修改页面数据及参数传递过程详解
Sep 27 Javascript
vue打包通过image-webpack-loader插件对图片压缩优化操作
Nov 12 Javascript
基于javascript实现移动端轮播图效果
Dec 21 Javascript
使用canvas进行图像编辑的实例
Aug 29 #Javascript
jQuery实现锚点向下平滑滚动特效示例
Aug 29 #jQuery
js使用html2canvas实现屏幕截取的示例代码
Aug 28 #Javascript
JavaScript编写棋盘覆盖代码详解
Aug 28 #Javascript
Angular4开发解决跨域问题详解
Aug 28 #Javascript
JavaScript实现各种排序的代码详解
Aug 28 #Javascript
JS如何设置元素样式的方法示例
Aug 28 #Javascript
You might like
PHP fopen 读取带中文URL地址的一点见解
2012/09/25 PHP
解析CodeIgniter自定义配置文件
2013/06/18 PHP
php读取文件内容到数组的方法
2015/03/16 PHP
php图片添加文字水印实现代码
2016/03/15 PHP
抛弃 PHP 代价太高
2016/04/26 PHP
php经典趣味算法实例代码
2020/01/21 PHP
深入理解JS中的变量及作用域、undefined与null
2014/03/04 Javascript
javascript为下拉列表动态添加数据项
2014/05/23 Javascript
JavaScript字符串对象slice方法入门实例(用于字符串截取)
2014/10/16 Javascript
javascript常用代码段搜集
2014/12/04 Javascript
js实现漂浮回顶部按钮实例
2015/05/06 Javascript
移动端翻页插件dropload.js(支持Zepto和jQuery)
2016/07/27 Javascript
浅谈angular懒加载的一些坑
2016/08/20 Javascript
vue.js指令v-model使用方法
2017/03/20 Javascript
js实现股票实时刷新数据案例
2017/05/14 Javascript
jQuery 添加样式属性的优先级别方法(推荐)
2017/06/08 jQuery
JS运动特效之任意值添加运动的方法分析
2018/01/24 Javascript
浅谈super-vuex使用体验
2018/06/25 Javascript
如何解决日期函数new Date()浏览器兼容性问题
2019/09/11 Javascript
vue制作抓娃娃机的示例代码
2020/04/17 Javascript
python实现决策树ID3算法的示例代码
2018/05/30 Python
对Python random模块打乱数组顺序的实例讲解
2018/11/08 Python
Python 实现域名解析为ip的方法
2019/02/14 Python
python实现比对美团接口返回数据和本地mongo数据是否一致示例
2019/08/09 Python
Python多进程multiprocessing、进程池用法实例分析
2020/03/24 Python
CSS3制作炫酷带方向感应的鼠标滑过图片3D动画
2016/03/16 HTML / CSS
加拿大时装零售商:Influence U
2018/12/22 全球购物
Under Armour安德玛德国官网:美国高端运动科技品牌
2019/03/09 全球购物
Java基础知识面试要点
2016/07/29 面试题
linux面试题参考答案(9)
2016/01/29 面试题
平面设计师工作职责范文
2013/12/03 职场文书
学生评语大全
2014/04/18 职场文书
2014年底工作总结
2014/12/15 职场文书
2015年员工工作表现评语
2015/03/25 职场文书
linux下导入、导出mysql数据库命令的实现方法
2021/05/26 MySQL
mysql事务隔离级别详情
2021/10/24 MySQL