JavaScript设计模式与开发事件阅读总结(二)

JavaScript设计模式与开发事件阅读总结(二)


设计模式的主题:将不变的部分和变化的部分隔开

一、单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
实现原理:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。
重点:创建对象和管理单例的职责被分布在两个不同的方法中。
应用场景:

  1. 一个网站的登录,点击登录后弹出一个登录弹框,即使再次点击,也不会再出现一个相同的弹框。
  2. 一个音乐播放程序,如果用户打开了一个音乐,又想打开一个音乐,那么之前的播放界面就会自动关闭,切换到当前的播放界面。
var Singleton = function (name) {
  this.name = name;
  this.instance = null;
};
Singleton.prototype.getName = function () {
  alert(this.name);
};
Singleton.getInstance = function (name) {
  if (!this.instance) {
    this.instance = new Singleton(name);
  }
  return this.instance;
};
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
alert(a === b); // true
var Singleton = function (name) {
  this.name = name;
};
Singleton.prototype.getName = function () {
  alert(this.name);
};
Singleton.getInstance = (function () {
  var instance = null;
  return function (name) {
    if (!instance) {
      instance = new Singleton(name);
    }
    return instance;
  };
})();
// 注意:立即执行函数只会执行一次,所有第二次调用此方法不会走var instance = null;这条语句
var a = Singleton.getInstance('sven1'); // 会执行Singleton.getInstance整个函数体
var b = Singleton.getInstance('sven2'); // 只会执行return后面的这个函数
alert(a === b); // true

代理实现单例模式:把负责管理单例的逻辑移到了代理类 proxySingletonCreateDiv 中,CreateDiv 就变成了一个普通的类。

var CreateDiv = function (html) {
  this.html = html;
  this.init();
};
CreateDiv.prototype.init = function () {
  var div = document.createElement('div');
  div.innerHTML = this.html;
  document.body.appendChild(div);
};
var ProxySingletonCreateDiv = (function () {
  var instance;
  return function (html) {
    if (!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  };
})();
var a = new ProxySingletonCreateDiv('sven1');
var b = new ProxySingletonCreateDiv('sven2');
alert(a === b);

降低全局变量带来的命名污染的方法:使用命名空间、使用闭包封装私有变量。

惰性单例:在需要的时候才创建对象实例。实例对象总是在我们调用某个方法的时候才被创建。

// 管理单例的逻辑封装在getSingle函数内部
var getSingle = function (fn) {
  var result;
  return function () {
    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';
};

二、策略模式

定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
优点:

  1. 利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句;
  2. 提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它
    们易于切换,易于理解,易于扩展;
  3. 利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻
    便的替代方案。

js 策略模式

var strategies = {
  S: function (salary) {
    return salary * 4;
  },
  A: function (salary) {
    return salary * 3;
  },
  B: function (salary) {
    return salary * 2;
  }
};
var calculateBonus = function (level, salary) {
  return strategies[level](salary);
};
console.log(calculateBonus('S', 20000)); // 输出:80000
console.log(calculateBonus('A', 10000)); // 输出:30000

表单验证策略模式实现:

// 策略对象
var strategies = {
  isNonEmpty: function (value, errorMsg) {
    // 不为空
    if (value === '') {
      return errorMsg;
    }
  },
  minLength: function (value, length, errorMsg) {
    // 限制最小长度
    if (value.length < length) {
      return errorMsg;
    }
  },
  isMobile: function (value, errorMsg) {
    // 手机号码格式
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
      return errorMsg;
    }
  }
};
// Validator 类
var Validator = function () {
  this.cache = []; // 保存校验规则
};

Validator.prototype.add = function (dom, rule, errorMsg) {
  var ary = rule.split(':'); // 把 strategy 和参数分开
  this.cache.push(function () {
    // 把校验的步骤用空函数包装起来,并且放入 cache
    var strategy = ary.shift(); // 用户挑选的 strategy
    ary.unshift(dom.value); // 把 input 的 value 添加进参数列表
    ary.push(errorMsg); // 把 errorMsg 添加进参数列表
    return strategies[strategy].apply(dom, ary);
  });
};

Validator.prototype.start = function () {
  for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
    var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
    if (msg) {
      // 如果有确切的返回值,说明校验没有通过
      return msg;
    }
  }
};
// 客户调用代码
var validataFunc = function () {
  var validator = new Validator(); // 创建一个 validator 对象
  /***************添加一些校验规则****************/
  validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空');
  validator.add(registerForm.password, 'minLength:6', '密码长度不能少于 6 位');
  validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确');
  var errorMsg = validator.start(); // 获得校验结果
  return errorMsg; // 返回校验结果
};

var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () {
  var errorMsg = validataFunc(); // 如果 errorMsg 有确切的返回值,说明未通过校验
  if (errorMsg) {
    alert(errorMsg);
    return false; // 阻止表单提交
  }
};

三、代理模式

定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。
理解:当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
代理保护:用于控制不同权限的对象对目标对象的访问。
虚拟代理:把一些开销很大的对象,延迟到真正需要它的时候才去创建。

虚拟代理实现图片预加载:
先用一张 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里。

// 负责给 img 节点设置 src
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('file:// /C:/Users/svenzeng/Desktop/loading.gif');
      img.src = src;
    }
  };
})();
proxyImage.setSrc('http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');

单一职责原则:就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。

虚拟代理合并 HTTP 请求:
实际场景:当我们选中一个 checkbox 的时候,它对应就要向服务器发送一次请求,如此频繁的网络请求将会带来相当大的开销。
解决:通过一个代理函数来收集一段时间之内的请求,最后一次性发送给服务器。

var synchronousFile = function (id) {
  console.log('开始同步文件,id 为: ' + id);
};
var proxySynchronousFile = (function () {
  var cache = [], // 保存一段时间内需要同步的 ID
    timer; // 定时器
  return function (id) {
    cache.push(id);
    if (timer) {
      // 保证不会覆盖已经启动的定时器
      return;
    }
    timer = setTimeout(function () {
      synchronousFile(cache.join(',')); // 2 秒后向本体发送需要同步的 ID 集合
      clearTimeout(timer); // 清空定时器
      timer = null;
      cache.length = 0; // 清空 ID 集合
    }, 2000);
  };
})();
var checkbox = document.getElementsByTagName('input');
for (var i = 0, c; (c = checkbox[i++]); ) {
  c.onclick = function () {
    if (this.checked === true) {
      proxySynchronousFile(this.id);
    }
  };
}

缓存代理:为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。
缓存代理用于求乘积:

var proxyMult = (function () {
  var cache = {};
  return function () {
    var args = Array.prototype.join.call(arguments, ',');
    // 实现缓存的主要代码
    if (args in cache) {
      return cache[args];
    }
    return (cache[args] = mult.apply(this, arguments));
  };
})();

缓存代理的第二个应用场景:ajax 异步请求数据
遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。

四、迭代器模式

定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
js 内置了迭代器:forEach 迭代器、every 迭代器、some 迭代器、reduce 迭代器、map 迭代器和 fiter 迭代器。
使用场景:操作日志

五、发布-订阅模式(观察者模式)

定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
实现代码:

// 将方法暴露在全局使用
var Event = (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), // (1);
      fns = clientList[key];
    if (!fns || fns.length === 0) {
      // 如果没有绑定对应的消息
      return false;
    }
    for (var i = 0, fn; (fn = fns[i++]); ) {
      fn.apply(this, arguments); // (2) // arguments 是 trigger 时带上的参数
    }
  };
  remove = function (key, fn) {
    var fns = clientList[key];
    if (!fns) {
      // 如果 key 对应的消息没有被人订阅,则直接返回
      return false;
    }
    if (!fn) {
      // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
      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
  };
})();
fn = function (price) {
  console.log('价格= ' + price); // 输出:'价格=2000000'
};
Event.listen('squareMeter88', fn); // 订阅消息
Event.trigger('squareMeter88', 2000000); // 发布消息
Event.remove('squareMeter88'); // 解绑

六、模板方法模式

定义:由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hoki802

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值