常用的Javascript设计模式

一、构造函数模式
// 构造一个动物的函数
  function Animal(name,color){
    this.name = name;
    this.color = color;
    this.getName = function(){
      return this.name;
    }
  }
  // 实例一个对象
  var cat = new Animal('大毛','white');
  console.log(cat.getName()); // 大毛
二、工厂模式
function Animal(name,color){
    var o = new Object();
    o.name = name;
    o.color = color;
    o.getName = function(){
      return this.name;
    };
    return o;
  }

  var cat = new Animal('大毛','white');
  console.log(cat.getName()); // 大毛
三、模块模式
  var Car = (function(){
    var name = '大毛';
    function getName(){
      console.log(name);
    }
    function getColor(color){
      console.log(color);
    }
    return {
      name: getName,
      color: getColor
    }
  })();
  Car.name(); // 大毛
  Car.color('red'); // red
四、混合模式
// 混合模式 = 原型模式 + 构造函数模式
  function create(parentObj) {
    function F() {}
    F.prototype = parentObj;
    return new F();
  }

  function Animal(name, color) {
    this.name = name;
    this.color = color;
  }

  Animal.prototype.getName = function() {
    return this.name;
  }

  function Cat(name, color) {
    Animal.call(null, name, color);
    this.color = color;
  }

  Cat.prototype = create(Animal.prototype);

  Cat.prototype.getColor = function() {
    return this.color;
  }
  var cat = new Cat('大毛', 'white');
  console.log(cat.getColor()) // white
五、单例模式
var Single = (function() {
    var instance;
    function init() {
      // 生成单例的构造函数的代码
      return {};
    }
    return {
      // 如果该实例存在,则直接返回,否则就对其实例化
      getInstance: function() {
        if (!instance) {
          instance = init();
        }
        return instance;
      }
    }
  })();

  var obj1 = Single.getInstance();
  var obj2 = Single.getInstance();
  console.log(obj1 === obj2); // true
// 保证一个类仅有一个实例,并提供一个访问它的全局访问点,例如:线程池,全局缓存,登录浮窗
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../my_ajax.js"></script>
</head>

<body>
  <form action="">
    <input type="text" name="user">
    <input type="password" name="pwd">
    <input type="submit" value="登录">
</form>
    <script type="text/javascript">
      var submitObj = {
        form: document.forms[0],
        submitUrl: "data2.php",
        _init: function() {
          this.handSubmit();
        },
        handSubmit: function() {
          var that = this;
          this.form.onsubmit = function(e) {
            e.preventDefault(); //阻止表单提交默认行为
            if (!that.checkForm()) return;
            that.ajaxSubmit();
          }
        },
        checkForm: function() {
          return true; //可使用正则表达式验证
        },
        ajaxSubmit: function() {
          my_ajax.post(this.submitUrl, new FormData(this.form), this.submitResult)
        },
        submitResult: function(result) {
          console.log(result);
        }
      }
      submitObj._init();
    </script>
</body>

</html>
// 需要把单例的逻辑代码单独提取,然后使用惰性单例的方式,也就是返回方法;
// 只有在点击的时候,才会进行执行,javascript的单例;
// 跟类不一样,无需创建多余的构造函数这些,直接创建全局变量即可;
!(function() {
        //管理单例的逻辑代码,如果没有数据则创建,有数据则返回
        var getSingle = function(fn) { //参数为创建对象的方法
          var result;
          return function() { //判断是Null或赋值
            return result || (result = fn.apply(this, arguments));
          };
        };
        //创建登录窗口方法
        var createLoginLayer = function() {
          var div = document.createElement('div');
          div.innerHTML = '我是登录浮窗';
          div.style.display = 'none';
          document.body.appendChild(div);
          return div;
        };
        //单例方法
        var createSingleLoginLayer = getSingle(createLoginLayer);

        //使用惰性单例,进行创建
        document.getElementById('loginBtn').onclick = function() {
          var loginLayer = createSingleLoginLayer();
          loginLayer.style.display = 'block';
        };
      })()

六、发布订阅者模式

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>

<body>
  <button id="btn1">按钮一</button>
  <button id="btn2">按钮二</button>
  <script type="text/javascript">
    var ObserverEvent = (function() {
      var clientList = [],listen, trigger, remove;
      listen = function(key, fn) {
        if (!clientList[key]) {
          clientList[key] = [];
        }
        clientList[key].push(fn);
      };
      trigger = function() {
        var key = Array.prototype.shift.call(arguments),
          fns = clientList[key];
        if (!fns || fns.length === 0) {
          return false;
        }
        for (var i = 0, fn; fn = fns[i++];) {
          fn.apply(this, arguments);
        }
      };
      remove = function(key, fn) {
        var fns = clientList[key];
        if (!fns) {
          return false;
        }
        if (!fn) {
          fns && (fns.length = 0);
        } else {
          for (var l = fns.length - 1; l >= 0; l--) {
            var _fn = fns[l];
            if (_fn === fn) {
              fns.splice(l, 1);
            }
          }
        }
      };
      return {
        listen: listen,
        trigger: trigger,
        remove: remove
      }
    })();
    ObserverEvent.listen('squareMeter88', fn1 = function(price) {
      console.log('价格=' + price);
    });
    ObserverEvent.listen('squareMeter100', function(price) {
      console.log('价格=' + price);
    });
    ObserverEvent.trigger('squareMeter88', 200000);
    ObserverEvent.trigger('squareMeter100', 300000);
    ObserverEvent.remove('squareMeter88', fn1);
    ObserverEvent.trigger('squareMeter88', 200000);
  </script>
</body>

</html>
var EventCenter = (function() {
      var events = {};

      // 绑定事件 添加回调
      function on(evt, handler) {
        events[evt] = events[evt] || [];
        events[evt].push({
          handler: handler
        })
      }

      function fire(evt, arg) {
        if (!events[evt]) {
          return
        }
        for (var i = 0; i < events[evt].length; i++) {
          events[evt][i].handler(arg);
        }
      }

      function off(evt) {
        delete events[evt];
      }
      return {
        on: on,
        fire: fire,
        off: off
      }
    }());

    var number = 1;
    EventCenter.on('click', function(data) {
      console.log('click 事件' + data + number++ + '次');
    });
    EventCenter.off('click'); //  只绑定一次
    EventCenter.on('click', function(data) {
      console.log('click 事件' + data + number++ + '次');
    });

    EventCenter.fire('click', '绑定');

七、适配器模式
       适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作,俗称包装器(wrapper);

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../my_ajax.js"></script>
</head>
<body>
<p id="p1"></p>
<p id="p2"></p>
​
<script type="text/javascript">
    //适配器模式:在不修改旧的模式的前提下,来适应新的变化
    my_ajax.get("data3.json",function (result) {
        showMsg(JSON.parse(result),p1);
    })
    my_ajax.get("data4.json",function (result) {
        showMsgAdapter(JSON.parse(result),p2);
    })
    function showMsg(obj,p) {
        p.innerHTML = obj.name;
    }
    function showMsgAdapter(arr,p) {
        showMsg(arr[0],p2);
    }
</script>
</body>
</html>
八、代理模式

       当客户不方便直接访问一个对象的时候,需要提供一个替身对象来控制对这个对象的访问,即把对一个对象的访问, 交给另一个代理对象来操作,代理模式分为:虚拟代理和保护代理:

  • 虚拟代理:把一些开销很大的对象,延迟到真正需要它的时候才去创建;
  • 保护代理:用于控制不同权限的对象对目标对象的访问;
图片预加载

       使用虚拟代理可以完成图片预加载功能,先用一张loading图片占位,然后用异步方式加载图片,等图片加载完毕后填充到img节点里;

// javascript事件均为异步事件,当执行proxyImage时,会先设置loading.gif;
// 等图片加载完毕后,会执行myImage操作;
      var myImage = (function() {
        var imgNode = document.createElement('img');
        document.body.appendChild(imgNode);
        return {
          setSrc: function(src) {
            imgNode.src = src;
          }
        };
      })();
      //预加载方法
      var proxyImage = (function() {
        var img = new Image();
        img.onload = function() {
          myImage.setSrc(this.src);
        }
        return {
          setSrc: function(src) {
            myImage.setSrc("loading.gif");
            img.src = src;
          }
        };
      })();
      proxyImage.setSrc('实际图片.jpg'); //预加载
      myImage.setSrc('实际图片'.jpg); //普通加载
// 加载方法和预加载方法,必须使用立即执行函数,否则setSrc方法调用不到;
// 如果不使用代理模式,会执行加载图片和预加载操作,当我们不需要预加载功能时,无法进行快速隔离
// 这种懒加载方法不用代理模式也可以实现,代理模式可以让 myImage 只做一件事,即只负责将实际图片加入到页面中;
// 而loading图片交给proxyImage去做,从而降低代码的耦合度;
// 当不想用loading的时候,可以直接调用 myImage 方法,即不需要代理对象的话,直接可以换成本体对象调用该方法即可;

参考链接:
常见的6种JavaScript设计模式
Javascript设计模式
常用的Javascript设计模式
js中的几种设计模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359

推荐阅读更多精彩内容