Javascript设计模式记录,这个方面确实是没写过,工作中也没有用到js设计模式的地方。

prototype与面向对象取舍

使用prototype原型继承和使用面向对象,都可以实现闭包的效果。那么这两个的选择点,就是方法会不会产生多个实例

例如,我们需要做一个闭包数组,并给他提供一个添加方法。

  1. !(function () {
  2. //原型继承写法
  3. var Validator = function(){
  4. this.cache = [];
  5. };
  6. Validator.prototype.add = function(item){
  7. this.cache.push(item);
  8. };
  9. var validator = new Validator(),validatorr = new Validator();
  10. validator.add("test1"); console.log(validator.cache);
  11. validatorr.add("test2"); console.log(validatorr.cache);
  12. //面向对象写法
  13. var Validator2 = {
  14. cache : [],
  15. add : function(item){
  16. this.cache.push(item);
  17. }
  18. };
  19. Validator2.add("test3"); console.log(Validator2.cache);
  20. Validator2.add("test4"); console.log(Validator2.cache);
  21. })()

prototype

这两种写法都可以实现闭包,但是面向对象的写法,只能存在一个。我们无法对他进行初始化,而原型继承写法,我们则可以对他进行初始化操作。

所以当,我们认为这个方法,在整个程序中,是唯一的存在。我们可以使用面向对象的写法,如果可以存在多个,则使用prototype这种写法。

调用父类构造函数

继承关系的两个对象,在实例的过程中,可以通过修改指向,来调整调用构造函数。

  1. !(function () {
  2. var A = function (light) {
  3. this.light1 = light;
  4. };
  5. var B = function (light) {
  6. this.light = light;
  7. A.apply(this,arguments);//你需要手动调用A的构造方法
  8. };
  9. //给B赋值的同时,给A赋值
  10. B.prototype = new A();
  11. var C = new B(123);
  12. console.log(C.light);
  13. console.log(C.light1);
  14. })()

构造函数

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。例如:线程池,全局缓存,登录浮窗。

首先我们需要把单例的逻辑代码单独提取,然后使用惰性单例的方式,也就是返回方法。只有在点击的时候,才会进行执行。

javascript的单例,跟类不一样。无需创建多余的构造函数这些,直接创建全局变量即可。

  1. !(function () {
  2. //管理单例的逻辑代码,如果没有数据则创建,有数据则返回
  3. var getSingle = function(fn){ //参数为创建对象的方法
  4. var result;
  5. return function(){ //判断是Null或赋值
  6. return result || (result = fn.apply(this,arguments));
  7. };
  8. };
  9. //创建登录窗口方法
  10. var createLoginLayer = function(){
  11. var div = document.createElement('div');
  12. div.innerHTML = '我是登录浮窗';
  13. div.style.display = 'none';
  14. document.body.appendChild(div);
  15. return div;
  16. };
  17. //单例方法
  18. var createSingleLoginLayer = getSingle(createLoginLayer);
  19.  
  20. //使用惰性单例,进行创建
  21. document.getElementById('loginBtn').onclick = function(){
  22. var loginLayer = createSingleLoginLayer();
  23. loginLayer.style.display = 'block';
  24. };
  25. })()

单例模式

策略模式

定义一系列的算法,把它们一个一个封装起来。将算法的使用与算法的实现分离开来。

javascript的策略模式很简单,把算法直接定义成函数即可。

  1. !(function () {
  2. //定义算法方法
  3. var strategies = {
  4. "S":function(salary){
  5. return salary * 4;
  6. },
  7. "A":function(salary){
  8. return salary * 3;
  9. },
  10. "B":function(salary){
  11. return salary * 2;
  12. }
  13. };
  14. //执行算法
  15. var calculateBouns = function(level,salary){
  16. return strategies[level](salary);
  17. };
  18. console.log(calculateBouns('S',2000));
  19. })()

策略模式

undefined终止循环

(写具体代码之前,先记录一个知识点)。当循环表达式为undefined时,循环会终止。

  1. !(function(){
  2. var cale = [1,2,3];
  3. for(var i= 0,validate;validate=cale[i];)
  4. {
  5. cale.shift();
  6. console.log(validate);
  7. }
  8. })() //1,2,3

登录验证表单

下面写一个使用策略模式,制作的验证表单登录效果。

传统的表单登录效果,会在提交后进行一系列的判断验证。这样提交方法很庞大,而且缺少弹性,复用性也很差。

我们可以使用策略模式,来避免这些问题。

  1. <form action="post" id="registerForm">
  2. <input type="text" name="userName" />
  3. <input type="text" name="password" />
  4. <input type="text" name="phoneNumber" />
  5. <button>提交</button>
  6. </form>

页面

  1. !(function () {
  2. //定义验证规则,使用策略模式,直接通过 strategies[isNonEmpty]()可以访问
  3. var strategies = {
  4. isNonEmpty: function (value, errorMsg) { //不为空
  5. if (value === "") {
  6. return errorMsg;
  7. }
  8. },
  9. minLength: function (value, length, errorMsg) { //最小长度
  10. if (value.length < length) {
  11. return errorMsg;
  12. }
  13. },
  14. isMobile: function (value, errorMsg) { //手机号码格式
  15. if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
  16. return errorMsg;
  17. }
  18. }
  19. };
  20. //创建验证逻辑,使用闭包定义全局数组,存放验证方法
  21. var Validator = function () {
  22. this.cache = [];
  23. };
  24. //添加验证逻辑方法,参数:元素,验证名称,错误信息
  25. Validator.prototype.add = function (dom, rules) {
  26. var self = this;
  27. for (var i = 0, rule; rule = rules[i++];) {
  28. (function (rule) {
  29. var ary = rule.strategy.split(":"); //限制最小数值,进行分割。如没有:号,则直接返回
  30. var errorMsg = rule.errorMsg;
  31. self.cache.push(function () { //将操作方法封装到全局数组中
  32. var strategy = ary.shift(); //获取验证方法名称,并删除
  33. ary.unshift(dom.value); //往开头添加待验证元素
  34. ary.push(errorMsg); //添加验证失败错误信息
  35. return strategies[strategy].apply(dom, ary); //传递数组给方法,因为不涉及this,dom也可传递null
  36. });
  37. })(rule)
  38. }
  39. };
  40. //添加启动方法
  41. Validator.prototype.start = function () {
  42. //将数组中的方法,分别执行。数组undefined,则跳出循环
  43. for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
  44. var msg = validatorFunc();
  45. //又失败就进行跳出
  46. if (msg) {
  47. return msg;
  48. }
  49. }
  50. };
  51. //处理校验
  52. var validatorFunc = function () {
  53. var validator = new Validator(); //创建验证逻辑\
  54. //添加验证条件
  55. validator.add(registerForm.userName, [
  56. {
  57. strategy: 'isNonEmpty',
  58. errorMsg: '用户不能为空'
  59. }, {
  60. strategy: 'minLength:2',
  61. errorMsg: '用户不能少于2位'
  62. }]);
  63. validator.add(registerForm.password, [
  64. {
  65. strategy: 'minLength:6',
  66. errorMsg: '密码长度不能少于6位'
  67. }]);
  68. validator.add(registerForm.phoneNumber, [
  69. {
  70. strategy: 'isMobile',
  71. errorMsg: '手机号码格式不正确'
  72. }]);
  73. var errorMsg = validator.start();
  74. return errorMsg;
  75. };
  76. var registerForm = document.getElementById("registerForm");
  77. registerForm.onsubmit = function () {
  78. var errorMsg = validatorFunc();
  79. if (errorMsg) { //判断是否有这个参数
  80. alert(errorMsg);
  81. return false;
  82. }
  83. };
  84. })()

javascript

果然,有逻辑的js,非常好玩。比CSS好玩多了。感觉有很多委托、多态的思想。

策略模式的优点与缺点

  1. 有效的避免许多重复的复制粘贴作业。
  2. 开闭原则的完美支持,算法完全独立易于切换、理解、拓展。
  3. 算法复用性强
  4. 使用组合和委托让Validator类拥有执行算法的能力,也是继承的一种轻便替代方式

缺点

  1. 会增加许多策略类或策略对象。
  2. 违反迪米特法则,会将strategy暴露给客户所有实现。

代理模式

为一个对象提供一个代用品或占位符,以便控制对它的访问。当客户不方便直接访问一个对象的时候,需要提供一个替身对象来控制对这个对象的访问。

代理模式分为:虚拟代理和保护代理

虚拟代理:把一些开销很大的对象,延迟到真正需要它的时候才去创建。

保护代理:用于控制不同权限的对象对目标对象的访问。

图片预加载

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

因为javascript事件,均为异步事件。所以当执行proxyImage时,会先设置loading.gif,等图片加载完毕后,会执行myImage操作。

  1. var myImage = (function(){
  2. var imgNode = document.createElement('img');
  3. document.body.appendChild(imgNode);
  4. return {
  5. setSrc:function(src){
  6. imgNode.src = src;
  7. }
  8. };
  9. })();
  10. //预加载方法
  11. var proxyImage = (function(){
  12. var img = new Image();
  13. img.onload = function(){
  14. myImage.setSrc(this.src);
  15. }
  16. return {
  17. setSrc:function(src){
  18. myImage.setSrc("loading.gif");
  19. img.src = src;
  20. }
  21. };
  22. })();
  23. proxyImage.setSrc('实际图片.jpg'); //预加载
  24.   myImage.setSrc('实际图片'.jpg); //普通加载

图片异步加载

注意:加载方法和预加载方法,必须使用立即执行函数,不然setSrc方法调用不到。

如上预加载功能,之所以使用代理模式,主要是为了避免违反,单一职责设计原则。

如不使用代理模式,会执行加载图片和预加载操作。当我们不需要预加载功能的时候,无法进行快速隔离。

虚拟代理中的惰性加载

将虚拟代理运用到惰性加载中,可以让真实的代码延迟到真正实用的时候才进行添加。

  1. var miniConsole = (function () {
  2. var cache = [];
  3. var handler = function (ev) { //监听按键事件
  4. if (ev.keyCode === 113) {
  5. var script = document.createElement('script');
  6. script.onload = function () {
  7. for (var i = 0, fn; fn = cache[i++];) {
  8. fn();
  9. }
  10. };
  11. script.src = 'minConsole.js';
  12. document.getElementsByTagName('head')[0].appendChild(script);
  13. document.body.removeEventListener('keydown', handler); //只加载一次
  14. }
  15. };
  16. document.body.addEventListener('keydown', handler, false);
  17. return {
  18. log: function () {
  19. var args = arguments;
  20. cache.push(function () {
  21. return miniConsole.log.apply(miniConsole, args);
  22. });
  23. }
  24. };
  25. })();
  26. miniConsole.log(11);
  27. miniConsole = {
  28. log: function () {
  29. console.log(Array.prototype.join.call(arguments));
  30. }
  31. };

惰性加载

缓存代理

缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,可以使用之前的计算结果。

例如使用缓存代理计算乘积

  1. var mult = function () {
  2. var a = 1;
  3. for (var i = 0, l = arguments.length; i < l; i++) {
  4. a = a * arguments[i];
  5. }
  6. return a;
  7. }
  8. //不使用缓存mult(2,3);
  9. var proxyMult = (function () {
  10. var cache = {};
  11. return function () {
  12. var args = Array.prototype.join.call(arguments, ','); //把参数放在一个字符串里
  13. if (args in cache) {
  14. return cache[args];
  15. }
  16. return cache[args] = mult.apply(this,arguments);
  17. };
  18. })();
  19. //使用缓存proxyMult(2,3)

缓存代理

代理工厂

传入高阶函数可以为各种计算方法创建缓存代理。将计算方法传入专门用于创建缓存代理的工厂中,这样就可以创建缓存代理了。

这是使用策略模式,进行创建的一种写法。可以直接定义方法即可。

  1. var strate = {
  2. mult: function () {
  3. var a = 1;
  4. for (var i = 0, l = arguments.length; i < l; i++) {
  5. a = a * arguments[i];
  6. }
  7. return a;
  8. },
  9. plus: function () {
  10. var a = 0;
  11. for (var i = 0, l = arguments.length; i < l; i++) {
  12. a = a + arguments[i];
  13. }
  14. return a;
  15. }
  16. };
  17. var createProxyFactory = function (fn) {
  18. var cache = {};
  19. return function () {
  20. var args = Array.prototype.join.call(arguments, ',');
  21. if (args in cache) {
  22. return cache[args];
  23. }
  24. return cache[args] = fn.apply(this, arguments);
  25. };
  26. };
  27. var proxyMult = createProxyFactory(strate["mult"]);
  28. console.log(proxyMult(1,2,3,4));

代理工厂

观察者模式

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

例如事件绑定,就是一个标准的观察者模式。

  1. document.body.addEventListener('click',function(){
  2. console.log();
  3. },false);
  4. document.body.click();

下面进行一个实际的例子,售楼处可以接受买房登记,登记后的用户如果有房源,则会逐一告知。 并且可以进行取消登记操作。

为了彻底结束耦合性,可以使用全局变量制作监听事件。

  1. var ObserverEvent = (function () {
  2. var clientList = [], listen, trigger, remove;
  3. listen = function (key, fn) {
  4. if (!clientList[key]) {
  5. clientList[key] = [];
  6. }
  7. clientList[key].push(fn);
  8. };
  9. trigger = function () {
  10. var key = Array.prototype.shift.call(arguments), fns = clientList[key];
  11. if (!fns || fns.length === 0) {
  12. return false;
  13. }
  14. for (var i = 0, fn; fn = fns[i++];) {
  15. fn.apply(this, arguments);
  16. }
  17. };
  18. remove = function (key, fn) {
  19. var fns = clientList[key];
  20. if (!fns) {
  21. return false;
  22. }
  23. if (!fn) {
  24. fns && (fns.length = 0);
  25. } else {
  26. for (var l = fns.length - 1; l >= 0; l--) {
  27. var _fn = fns[l];
  28. if (_fn === fn) {
  29. fns.splice(l, 1);
  30. }
  31. }
  32. }
  33. };
  34. return {
  35. listen:listen,
  36. trigger:trigger,
  37. remove:remove
  38. }
  39. })();
  40. ObserverEvent.listen('squareMeter88', fn1 = function (price) {
  41. console.log('价格=' + price);
  42. });
  43. ObserverEvent.listen('squareMeter100', function (price) {
  44. console.log('价格=' + price);
  45. });
  46. ObserverEvent.trigger('squareMeter88', 200000);
  47. ObserverEvent.trigger('squareMeter100', 300000);
  48. ObserverEvent.remove('squareMeter88', fn1);
  49. ObserverEvent.trigger('squareMeter88', 200000);

售楼处例子

当然这种售楼处只是一个例子,在现实中,登录页面登录后,会需要刷新各个模块的信息(头像、nav)这类。我们也可以使用观察者模式进行刷新操作。

我们直接改用调用方法即可,而且是完全的解耦合

  1. var header = (function () {
  2. ObserverEvent.listen('loginSucc', function (data) {
  3. header.setAvatar(data.avatar);
  4. });
  5. return {
  6. setAvatar: function (data) {
  7. console.log(data + "设置header成功");
  8. }
  9. }
  10. })();
  11. var nav = (function () {
  12. ObserverEvent.listen('loginSucc', function (data) {
  13. nav.setAvatar(data.avatar)
  14. });
  15. return {
  16. setAvatar: function (data) {
  17. console.log(data + '设置nav成功');
  18. }
  19. }
  20. })();
  21. var data = {};
  22. data.avatar = "参数";
  23. ObserverEvent.trigger('loginSucc', data);

刷新模块信息

观察者模式的优点很明显:时间上的解耦,对象之间的解耦。

命令模式

命令模式指的是一个执行某些特定事情的指令。常见的应用场景是:有时候需要向对象发送请求,但不知道接受者是谁,也不知道请求的操作是什么。

例如:订餐,客人需要给厨师发送请求,至于那个厨师做,做的步骤。客人不知道。这就是命令模式。

命令模式的一个简单例子

  1. //定义命令模式执行
  2. var setCommand = function (button, func) {
  3. button.onclick = function () {
  4. func.execute();
  5. };
  6. };
  7. var MenuBar = {
  8. refresh: function () {
  9. console.log("刷新页面");
  10. }
  11. };
  12. var RefreshMenuBarCommand = function (receiver) {
  13. return {
  14. execute: function () {
  15. receiver.refresh();
  16. }
  17. }
  18. };
  19. var refreshMenuBarCommand = RefreshMenuBarCommand("MenuBar");
  20. setCommand(button1,refreshMenuBarCommand);

命令模式

命令模式的用处很大,也可以做撤销命令,回放这种功能。比如,我们把用户按键命令做一个封装,制作一个播放功能。

以下代码可以执行并记录按键,当点击按钮时,会执行按键对应动作。

  1. //定义按键动作
  2. var Ryu = {
  3. W: function () {
  4. console.log("用户按下W");
  5. },
  6. S: function () {
  7. console.log("用户按下S");
  8. }
  9. };
  10. //创建命令
  11. var makeCommand = function (receiver, state) {
  12. return function () {
  13. if(receiver[state])
  14. receiver[state]();
  15. }
  16. };
  17. //可执行按键Json
  18. var commands = {
  19. "119": "W",
  20. "115": "S"
  21. };
  22. //保存按键记录
  23. var commandStack = [];
  24. document.onkeypress = function (ev) {
  25. var keyCode = ev.keyCode, command = makeCommand(Ryu, commands[keyCode]);
  26. if (command) {
  27. command();
  28. commandStack.push(command);
  29. }
  30. };
  31. //注册按键监听
  32. document.getElementById("replay").addEventListener('click', function () {
  33. var commad;
  34. while (command = commandStack.shift()) {
  35. command();
  36. }
  37. }, false);

按键监听

宏命令

宏命令可以一次执行一组命令。我们定义了各种指令,定义了如何执行指令。就可以做成一组命令的这种模式了。

针对不同的步骤,也可以只用此方法。例如:

  1. var macroCommand2 = new MacroCommand();
    macroCommand2.add(macroCommand);
    macroCommand2.execute();
    可以指定不同命令。
  1. !(function () {
  2. var closeDoorCommand = {
  3. execute: function () {
  4. console.log("关门");
  5. }
  6. };
  7. var openPcCommand = {
  8. execute: function () {
  9. console.log("开电脑");
  10. }
  11. };
  12. var openQQCommand = {
  13. execute: function () {
  14. console.log("登录QQ");
  15. }
  16. };
  17. var MacroCommand = function(){
  18. return {
  19. commandsList:[],
  20. add:function(command){
  21. this.commandsList.push(command);
  22. },
  23. execute:function(){
  24. for(var i= 0,command;command=this.commandsList[i++];){
  25. command.execute();
  26. }
  27. }
  28. };
  29. };
  30. var macroCommand = new MacroCommand();
  31. macroCommand.add(closeDoorCommand);
  32. macroCommand.add(openPcCommand);
  33. macroCommand.execute();
  34. })()

宏命令

模板方法模式

模板方法是一种只需要继承就可以实现的非常简单的模式。封装了子类的算法框架,包含一些公共方法一级封装子类中的所有方法执行顺序。

咖啡与茶。咖啡的步骤:1.烧水,2.冲泡,3.倒进杯子,4.放牛奶。茶叶的步骤:1.烧水,2.浸泡,3.倒进杯子,4.加柠檬

我们分离不同点:2,4。然后使用抽象父类定义,并实现对应方法。其中的init方法,就是模板方法,因为他封装了子类算法框架就,作为一个算法的模板。

  1. !(function () {
  2. var Beverage = function(){};
  3. Beverage.prototype.boilWater = function(){ //烧水
  4. console.log("把水煮沸");
  5. };
  6. Beverage.prototype.brew = function(){}; //第二步,方法
  7. Beverage.prototype.pourInCup = function(){};
  8. Beverage.prototype.addCondiments=function(){};
  9. Beverage.prototype.init = function(){
  10. this.boilWater();
  11. this.brew();
  12. this.pourInCup();
  13. this.addCondiments();
  14. };
  15. //创建咖啡子类
  16. var Coffee = function(){};
  17. Coffee.prototype = new Beverage();
  18. Coffee.prototype.brew = function(){
  19. console.log("用沸水冲泡咖啡");
  20. };
  21. Coffee.prototype.pourInCup = function(){
  22. console.log("把咖啡倒进杯子");
  23. };
  24. Coffee.prototype.addCondiments = function(){
  25. console.log("加糖加牛奶");
  26. };
  27. var Coffee = new Coffee();
  28. Coffee.init();
  29. })()

模板命令

针对模板方法,有一些个性的子类,不打算接受模板约束。那么可以使用钩子方法来创建。

我们修改模板方法让他适应钩子方法。

  1. Beverage.prototype.customerWantsCondiments = function () {
  2. return true;
  3. };
  4. Beverage.prototype.init = function () {
  5. this.boilWater();
  6. this.brew();
  7. this.pourInCup();
  8. if (this.customerWantsCondiments()) {
  9. this.addCondiments();
  10. }
  11. };
  12. //创建咖啡子类
  13. var Coffee = function () {
  14. };
  15. Coffee.prototype = new Beverage();
  16. Coffee.prototype.customerWantsCondiments = function(){
  17. return window.confirm("请问需要调料吗");
  18. }

钩子方法

享元模式

享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

例如,现在有50件男装和50件女装,分别需要模特穿上并且拍照。如果我们不使用享元模式,那么就需要new 100个模特。

使用享元模式,只需要new 2个模特,然后让他们穿上不同的衣服即可。

  1. !(function () {
  2. var Model = function (sex) {
  3. this.sex = sex;
  4. };
  5. Model.prototype.takePhoto = function () {
  6. console.log("sex=" + this.sex + " underwear=" + this.underwear);
  7. };
  8. var maleModel = new Model("male");
  9. var femaleModel = new Model("female");
  10. for(var i=1;i<=50;i++){
  11. maleModel.underwear = "underwear"+i;
  12. maleModel.takePhoto();
  13. }
  14. })()

享元模式

享元模式的使用取决于:一个程序中使用了大量相似对象。造成很大的内存开销。大多数状态是外部状态。可以用较少的功效对象取代大量对象。

对象池

对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接new,而是转从对象池里获取。如果没有空闲对象则创建,完成职责后再次进入池子。

我们做一个公用的对象池,来维护新建dom对象。

  1. !(function () {
  2. var objectPoolFactory = function (createObjFn) {
  3. var objectPool = [];
  4. return {
  5. create: function () {
  6. var obj = objectPool.length === 0 ? createObjFn.apply(this, arguments) : objectPool.shift();
  7. return obj;
  8. },
  9. recover: function (obj) {
  10. objectPool.push(obj);
  11. }
  12. };
  13. };
  14. var iframeFactory = objectPoolFactory(function () {
  15. var iframe = document.createElement("iframe");
  16. document.body.appendChild(iframe);
  17. iframe.onload = function () {
  18. iframe.onload = null;
  19. iframeFactory.recover(iframe);
  20. }
  21. return iframe
  22. });
  23. var iframe1 = iframeFactory.create();
  24. iframe1.src = "http://www.baidu.com";
  25.  
  26. setTimeout(function(){
  27. var iframe2 = iframeFactory.create();
  28. iframe2.src = "http://www.baidu.com";
  29. },2000);
  30. })()

对象池

职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将对象形成一条链,并沿着这条链传递请求。

使用orderType和pay来控制流向。分别进行不同对象的流转。

  1. !(function () {
  2. var order500 = function (orderType, pay, stock) {
  3. if (orderType === 1 && pay === true) {
  4. console.log("500元定金");
  5. } else {
  6. order200(orderType, pay, stock);
  7. }
  8. };
  9. var order200 = function (orderType, pay, stock) {
  10. if (orderType === 2 && pay === true) {
  11. console.log("200元定金");
  12. } else {
  13. orderNormal(orderType, pay, stock);
  14. }
  15. };
  16. var orderNormal = function (orderType, pay, stock) {
  17. if (stock > 0) {
  18. console.log("普通购买");
  19. } else {
  20. console.log("手机库存不足");
  21. }
  22. };
  23. order500(1,true,500);
  24. })()

职责链模式一

但是这种责任链体系,耦合度比较高,例如500对象与200对象,耦合度很高。

  1. !(function () {
  2. var order500 = function (orderType, pay, stock) {
  3. if (orderType === 1 && pay === true) {
  4. console.log("500元定金");
  5. } else {
  6. return "nextSuccessor";
  7. }
  8. };
  9. var order200 = function (orderType, pay, stock) {
  10. if (orderType === 2 && pay === true) {
  11. console.log("200元定金");
  12. } else {
  13. return "nextSuccessor";
  14. }
  15. };
  16. var orderNormal = function (orderType, pay, stock) {
  17. if (stock > 0) {
  18. console.log("普通购买");
  19. } else {
  20. console.log("手机库存不足");
  21. }
  22. };
  23. var Chain = function(fn){
  24. this.fn = fn;
  25. this.success = null;
  26. };
  27. Chain.prototype.setNextSuccessor = function(successor){
  28. return this.success = successor;
  29. };
  30. Chain.prototype.passRequest = function(){
  31. var ret = this.fn.apply(this,arguments);
  32. if(ret === "nextSuccessor"){
  33. return this.success && this.success.passRequest.apply(this.success,arguments);
  34. }
  35. };
  36. var chainOrder500 = new Chain(order500);
  37. var chainOrder200 = new Chain(order200);
  38. chainOrder500.setNextSuccessor(chainOrder200);
  39. chainOrder500.passRequest(2,true,200);
  40. })()

职责链模式二

中介者模式

中介者模式的作用是解除对象与对象之间的紧耦合关系。增加中介者后,所有的相关对象都通过中介者对象来通信。

泡泡堂例子

  1. !(function () {
  2. var playerDirector = (function () {
  3. var players = {}, operations = {};
  4. operations.addPlayer = function (player) {
  5. var teamColor = player.teamColor;
  6. players[teamColor] = players[teamColor] || [];
  7. players[teamColor].push(player);
  8. };
  9. operations.removePlayer = function (player) {
  10. var teamColor = player.teamColor, teamPlayers = players[teamColor] || [];
  11. for (var i = teamPlayers.length - 1; i >= 0; i++) {
  12. if (teamPlayers[i] === player) {
  13. teamPlayers.splice(i, 1);
  14. }
  15. }
  16. };
  17. operations.changeTeam = function (player, newTeamColor) {
  18. operations.removePlayer(player);
  19. player.teamColor = newTeamColor;
  20. operations.addPlayer(player);
  21. };
  22. operations.playerDead = function (player) {
  23. var teamColor = player.teamColor, teamPlays = players[teamColor];
  24. var all_dead = true;
  25. for (var i = 0, player; player = teamPlays[i++];) {
  26. if (player.state !== "dead") {
  27. all_dead = false;
  28. break;
  29. }
  30. }
  31. if (all_dead === true) {
  32. for (var i = 0, player; player = teamPlays[i++];) {
  33. player.lose();
  34. }
  35. for (var color in players) {
  36. if (color != teamColor) {
  37. var teamPlayers = players[color];
  38. for (var i = 0, player; player = teamPlayers[i++];) {
  39. player.win();
  40. }
  41. }
  42. }
  43. }
  44. };
  45. var ReceiveMessage = function () {
  46. var message = Array.prototype.shift.call(arguments);
  47. operations[message].apply(this, arguments);
  48. };
  49. return {
  50. ReceiveMessage:ReceiveMessage
  51. };
  52. })();
  53. function Player(name, teamColor) {
  54. this.name = name;
  55. this.teamColor = teamColor;
  56. this.state = "alive";
  57. }
  58. Player.prototype.win = function () {
  59. console.log(this.name + " win ");
  60. };
  61. Player.prototype.lose = function () {
  62. console.log(this.name + " lose ");
  63. }
  64. Player.prototype.die = function () {
  65. this.state = "dead";
  66. playerDirector.ReceiveMessage("playerDead", this);
  67. };
  68. Player.prototype.remove = function () {
  69. playerDirector.ReceiveMessage("removePlayer", this);
  70. };
  71. Player.prototype.changeTeam = function (color) {
  72. playerDirector.ReceiveMessage("changeTeam", this, color);
  73. };
  74. var PlayerFacotry = function (name, teamColor) {
  75. var newPlayer = new Player(name, teamColor);
  76. playerDirector.ReceiveMessage("addPlayer", newPlayer);
  77. return newPlayer;
  78. };
  79.  
  80. //测试
  81. var player1 = PlayerFacotry("皮蛋","red"),
  82. player2 = PlayerFacotry("小乖","red");
  83. var player3 = PlayerFacotry("黑妞","blue"),
  84. player4 = PlayerFacotry("葱头","blue");
  85. player1.die();player2.die();
  86. })()

泡泡堂例子

中介者模式是迎合迪米特法则的一种实现。指一个对象应该尽可能的少了解另外的对象。如果对象之间的耦合性太高,一个对象改变后,会影响其他对象。

装饰者模式

装饰者模式可以动态的给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

  1. !(function () {
  2. var plance = {
  3. fire:function(){
  4. console.log("发射普通子弹");
  5. }
  6. };
  7. var missileDecorator = function(){
  8. console.log("发射导弹");
  9. };
  10. var fire1 = plance.fire;
  11. plance.fire = function(){
  12. fire1();
  13. missileDecorator();
  14. };
  15. plance.fire();
  16. })()

装饰者模式

或者使用装饰函数(AOP)Function的after或者before

  1. !(function () {
  2. var plance = function () { };
  3. plance.prototype.fire = function () {
  4. console.log("发射普通子弹");
  5. };
  6. var missileDecorator = function () {
  7. console.log("发射导弹");
  8. };
  9. Function.prototype.after = function (afterfn) {
  10. var _self = this;
  11. return function () {
  12. var ret = _self.apply(this, arguments);
  13. afterfn.apply(this, arguments);
  14. return ret;
  15. };
  16. };
  17. var pl = new plance();
  18. pl.fire = pl.fire.after(missileDecorator);
  19. pl.fire();
  20. })()

使用AOP

装饰函数是一个很实用的功能,例如我们制作插件式的表单验证,就可以使用装饰函数。

  1. !(function () {
  2. var registerForm = document.getElementById("registerForm");
  3. Function.prototype.before = function(beforeFn){
  4. var _self = this;
  5. return function(){
  6. if(beforeFn.apply(this,arguments) === false){
  7. return;
  8. }
  9. return _self.apply(this,arguments);
  10. };
  11. }
  12. var validata = function () {
  13. if(registerForm.userName.value === ""){
  14. alert("用户名不能为空");
  15. return false;
  16. }
  17. };
  18. var formSubmit = function(){
  19. console.log("成功");
  20. }
  21. formSubmit = formSubmit.before(validata);
  22. registerForm.onsubmit = function(){
  23. formSubmit();
  24. return false;
  25. };
  26. })()

使用before登录验证

代理模式OR装饰器模式

代理模式和装饰器模式相似的地方很多,都是有单独的一个对象提供间接访问。他们最大的不同就是设计的意图和目的。

代理模式的目的是:当直接访问本体不方便或者不符合需求时,为这个本体提供一个替代者。

装饰器模式的目的是:为对象动态加入一些行为。

例如图片预加载,代理提供预加载功能是调用原来的方法也就是跟本体做的事情一样,而装饰器模式则是添加新的职责和行为。

状态模式

状态模式关键是区分事物内部的状态,事物内部状态改变会带来事物行为的改变。

第一个例子:电灯开关,同样是按下开关,电灯亮或者不亮,表达的行为是不一样的。buttonWasPressed 方法是变化的点。

  1. !(function () {
  2. var LightEvent = {
  3. on:function(){
  4. console.log("关灯");
  5. this.state = "off";
  6. },
  7. off:function(){
  8. console.log("开灯");
  9. this.state = "on";
  10. }
  11. };
  12. var Light = function () {
  13. this.state = "off";
  14. this.button = null;
  15. };
  16. Light.prototype.init = function(){
  17. var button = document.createElement("button"),self=this;
  18. button.innerHTML = "开关";
  19. this.button = document.body.appendChild(button);
  20. this.button.onclick = function(){
  21. self.buttonWasPressed();
  22. };
  23. };
  24. Light.prototype.buttonWasPressed = function(){
  25. LightEvent[this.state].apply(this,arguments);
  26. };
  27. var light = new Light();
  28. light.init();
  29. })()

电灯例子

上一个例子使用策略模式来完成的,我们来说一下策略模式与状态模式的区别

策略模式中各个策略类是平等又平行的,他们之间没有任何联系。状态类的行为早已被封装好了,改变行为发生在状态模式内部。

那么我是使用状态模式,来完成上面电灯的例子。

  1. !(function () {
  2. var delegate = function(client,delegation){
  3. return {
  4. buttonWasPressed:function(){
  5. return delegation.buttonWasPressed.apply(client,arguments);
  6. }
  7. };
  8. };
  9. var FSM = {
  10. off: {
  11. buttonWasPressed: function () {
  12. console.log("关灯");
  13. this.currState = this.onState;
  14. }
  15. },
  16. on: {
  17. buttonWasPressed: function () {
  18. console.log("开灯");
  19. this.currState = this.offState;
  20. }
  21. }
  22. };
  23. var Light = function () {
  24. this.offState = delegate(this,FSM.off);
  25. this.onState = delegate(this,FSM.on);
  26. this.currState = FSM.off;
  27. this.button = null;
  28. };
  29. Light.prototype.init = function () {
  30. var button = document.createElement("button"), self = this;
  31. button.innerHTML = "开关";
  32. this.button = document.body.appendChild(button);
  33. this.button.onclick = function () {
  34. self.currState.buttonWasPressed.call(self);
  35. };
  36. };
  37. var light = new Light();
  38. light.init();
  39. })()

状态机电灯例子

Javascript知识点记录(三)设计模式的更多相关文章

  1. JavaScript学习记录三

    title: JavaScript学习记录三 toc: true date: 2018-09-14 23:51:22 --<JavaScript高级程序设计(第2版)>学习笔记 要多查阅M ...

  2. javascript知识点记录(1)

    javascript一些知识点记录 1.substring,slice,substr的用法 substring 和slice 都有startIndex 和 endIndex(不包括endInex),区 ...

  3. Javascript知识点记录(二)

    Javascript入门易,精通难,基本上是共识的一个观点.在这个篇幅里,主要对一些难点进行记录. 鸭子类型 Javascript属于动态类型语言的一种.对变量类型的宽容,给了很大的灵活性.由于无需类 ...

  4. 高性能javascript(记录三)

    DOM(文档对象模型)是一个独立的语言,用于操作XML和HTML文档的程序接口(API).在游览器中,主要用来与HTML文档打交道,同样也用在Web程序中获取XML文档,并使用DOM API用来访问文 ...

  5. javascript知识点记录(2)

    1.js 异步加载和同步加载 异步加载模式也叫非阻塞模式,浏览器在下载js的同时,同时还会执行后续的页面处理, 在script标签内,用创建一个script元素,并插入到document中,这样就是异 ...

  6. JavaScript算法与数据结构知识点记录

    JavaScript算法与数据结构知识点记录 zhanweifu

  7. C#知识点记录

    用于记录C#知识要点. 参考:CLR via C#.C#并发编程.MSDN.百度 记录方式:读每本书,先看一遍,然后第二遍的时候,写笔记. CLR:公共语言运行时(Common Language Ru ...

  8. Javascript多线程引擎(三)

    Javascript多线程引擎(三) 完成对ECMAScript-262 3rd规范的阅读后, 列出了如下的限制条件 1. 去除正则表达式( 语法识别先不编写) 2. 去除对Function Decl ...

  9. JavaScript知识点整理(一)

    JavaScript知识点(一)包括 数据类型.表达式和运算符.语句.对象.数组. 一.数据类型 1) js中6种数据类型:弱类型特性 5种原始类型:number(数字).string(字符串).bo ...

随机推荐

  1. Ubuntu1404安装gogs过程

    一.Gogs介绍 Gogs (Go Git Service) 是一款极易搭建的自助 Git 服务. Gogs 的目标是打造一个最简单.最快速和最轻松的方式搭建自助 Git 服务.使用 Go 语言开发使 ...

  2. IIS 7.5 Application Warm-Up Module

    http://www.cnblogs.com/shanyou/archive/2010/12/21/1913199.html 有些web应用在可以处理用户访问之前,需要装载很多的数据,或做一些花费很大 ...

  3. 数据仓库之SSIS开发

    1.从cdc捕获到数据以后, 连接ssis进行执行数据的抽取以及转换工作,把需要的数据导入到数据仓库, 并且做好对应的日志记录表.现在先说一下比较重要的. 选择参数化设置数据连接, 以方便后面的配置. ...

  4. 查看数据库表的数据量和SIZE大小的脚本修正

    在使用桦仔的分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)的脚本时,遇到下面一些错误 这个是因为这些表的Schema是Maint,而不是默认的dbo,造成下面这段SQ ...

  5. UNIX系统的显示时间何时会到达尽头

    本文分为三个小块: 一.UNIX系统中时间的存储形式: 二. time_t 的最大值是多少: 三. 将time_t 的最大值转化为真实世界的时间: #---------------------# # ...

  6. 无需FQ,自建本地CDN,秒上StackOverFlow!

    StackOverflow是一个面向程序员的技术问答平台.可是在不FQ的情况下,浏览StackOverflow是一件让人极不舒服的事情,常常需要等待数十秒页面才慢慢显示出来.本文我教大家一种能够流畅地 ...

  7. linux 命令之grep

    grep主要用来在文件中进行正则查找 通常都会将高亮颜色打开,方便阅读,为grep建立一个别名alias放到.bashrc等文件中: alias grep='grep --color=auto' 最常 ...

  8. TAR,JAR,Zip的使用

    在文件归档的时候,LINUX中,我常喜欢使用tar,它可以把一个文件夹归档为一个文件,可以同时使用指定的压缩算法把其压缩归档. 最常用的语句是: tar cvzf target.tar.gz sour ...

  9. 浅谈Java中的对象和引用

    浅谈Java中的对象和对象引用 在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事,事实上则不然.今天我们就来一起 ...

  10. 【2016-11-7】【坚持学习】【Day22】【Oracle 递归查询】

    直接在oracle 递归查询语句 select * from groups start with id=:DeptId connect by prior superiorid =id 往下找 sele ...