[博文]JavaScript设计模式

JS的设计模式(23种) 抽象工厂模式提供一个接口用于创建一系列相关或相互依赖的对象,而无需指定具体类。客户端使用此接口选择所需的产品族中的产品对象。 阅读详情

导读:作者本人一直从事前端开发,文章以JavaScript语言的角度去论证,JavaScript模式与前端的工作和成长密不可分,因为这确实不是一个简单的话题,作者用简单表达和例子为大家阐明JavaScript设计模式。优快云研发频道对此文进行了整理选取部分内容,供开发者学习、参考。

内容如下:

前言:

JavaScript设计模式的作用——提高代码的重用性,可读性,使代码更容易的维护和扩展。

1.单体模式,工厂模式,桥梁模式个人认为这个一个优秀前端必须掌握的模式,对抽象编程和接口编程都非常有好处。

2.装饰者模式和组合模式有很多相似的地方,它们都与所包装的对象实现同样的接口并且会把任何方法的调用传递给这些对象。装饰者模式和组合模式是本人描述的较吃力的两个模式,我个人其实也没用过,所以查了很多相关资料和文档,请大家海涵。

3.门面模式是个非常有意思的模式,几乎所有的JavaScript库都会用到这个模式,假如你有逆向思维或者逆向编程的经验,你会更容易理解这个模式(听起来有挑战,其实一接触你就知道这是个很简单的模式);还有配置器模式得和门面模式一块拿来说,这个模式对现有接口进行包装,合理运用可以很多程度上提高开发效率。这两个模式有相似的地方,所以一块理解的话相信都会很快上手的。

4.享元模式是一种以优化为目的的模式。

5.代理模式主要用于控制对象的访问,包括推迟对其创建需要耗用大量计算资源的类得实例化。

6.观察者模式用于对对象的状态进行观察,并且当它发生变化时能得到通知的方法。用于让对象对事件进行监听以便对其作出响应。观察者模式也被称为“订阅者模式”。

7.命令模式是对方法调用进行封装的方式,用命名模式可以对方法调用进行参数化和传递,然后在需要的时候再加以执行。

8.职责链模式用来消除请求的发送者和接收者之间的耦合。

JavaScript设计模式都有哪些?

单体(Singleton)模式: 绝对是JavaScript中最基本最有用的模式。

单体在JavaScript的有多种用途,它用来划分命名空间。可以减少网页中全局变量的数量(在网页中使用全局变量有风险);可以在多人开发时避免代码的冲突(使用合理的命名空间)等等。

在中小型项目或者功能中,单体可以用作命名空间把自己的代码组织在一个全局变量名下;在稍大或者复杂的功能中,单体可以用来把相关代码组织在一起以便日后好维护。

使用单体的方法就是用一个命名空间包含自己的所有代码的全局对象,示例:

  1. var functionGroup = {  
  2.   name:'Darren',  
  3.   method1:function(){  
  4.   //code  
  5.   },  
  6. init:function(){  
  7. //code  
  8.   }  
  9. } 

或者

  1. var functionGroup  = new function myGroup(){  
  2.   this.name = 'Darren';  
  3.   this.getName = function(){  
  4. return this.name  
  5.   }  
  6. this.method1 = function(){}  
  7. ...  
  8. } 

工厂(Factory)模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。

工厂就是把成员对象的创建工作转交给一个外部对象,好处在于消除对象之间的耦合(何为耦合?就是相互影响)。通过使用工厂方法而不是new关键字及具体类,可以把所有实例化的代码都集中在一个位置,有助于创建模块化的代码,这才是工厂模式的目的和优势。

举个例子:你有一个大的功能要做,其中有一部分是要考虑扩展性的,那么这部分代码就可以考虑抽象出来,当做一个全新的对象做处理。好处就是将来扩展的时候容易维护——只需要操作这个对象内部方法和属性,达到了动态实现的目的。非常有名的一个示例——XHR工厂:

  1.  var XMLHttpFactory = function(){}; //这是一个简单工厂模式  
  2.   XMLHttpFactory.createXMLHttp = function(){  
  3.  var XMLHttp = null;  
  4.   if (window.XMLHttpRequest){  
  5.   XMLHttp = new XMLHttpRequest()  
  6.  }else if (window.ActiveXObject){  
  7.   XMLHttp = new ActiveXObject("Microsoft.XMLHTTP")  
  8.   }  
  9.   return XMLHttp;  
  10.   }  
  11.   //XMLHttpFactory.createXMLHttp()这个方法根据当前环境的具体情况返回一个XHR对象。  
  12. var AjaxHander = function(){  
  13. var XMLHttp = XMLHttpFactory.createXMLHttp();  
  14.   ...  
  15. } 

工厂模式又区分简单工厂模式和抽象工厂模式,上面介绍的是简单工厂模式,这种模式用的更多也更简单易用。抽象工厂模式的使用方法就是 - 先设计一个抽象类,这个类不能被实例化,只能用来派生子类,最后通过对子类的扩展实现工厂方法。 示例:

  1. var XMLHttpFactory = function(){};      //这是一个抽象工厂模式  
  2.   XMLHttpFactory.prototype = {  
  3.      //如果真的要调用这个方法会抛出一个错误,它不能被实例化,只能用来派生子类  
  4.      createFactory:function(){  
  5.         throw new Error('This is an abstract class');  
  6.     }  
  7.   }  
  8. //派生子类,文章开始处有基础介绍那有讲解继承的模式,不明白可以去参考原理  
  9. var XHRHandler = function(){  
  10.      XMLHttpFactory.call(this);  
  11.   };  
  12. XHRHandler.prototype = new XMLHttpFactory();  
  13.   XHRHandlerXHRHandler.prototype.constructor = XHRHandler;  
  14.   //重新定义createFactory 方法  
  15.   XHRHandler.prototype.createFactory = function(){  
  16.      var XMLHttp = null;  
  17.      if (window.XMLHttpRequest){  
  18.         XMLHttp = new XMLHttpRequest()  
  19.      }else if (window.ActiveXObject){  
  20.         XMLHttp = new ActiveXObject("Microsoft.XMLHTTP")  
  21.      }  
  22.      return XMLHttp;  
  23.   } 

桥梁(bridge)模式:在实现API的时候,桥梁模式灰常有用。在所有模式中,这种模式最容易立即付诸实施。

桥梁模式可以用来弱化它与使用它的类和对象之间的耦合,就是将抽象与其实现隔离开来,以便二者独立变化;这种模式对于JavaScript中常见的时间驱动的编程有很大益处,桥梁模式最常见和实际的应用场合之一是时间监听器回调函数。先分析一个不好的示例:

  1. element.onclick = function(){  
  2.     new setLogFunc();  
  3.   }; 

为什么说这个示例不好,因为从这段代码中无法看出那个LogFunc方法要显示在什么地方,它有什么可配置的选项以及应该怎么去修改它。换一种说法就是,桥梁模式的要诀就是让接口“可桥梁”,实际上也就是可配置。把页面中一个个功能都想象成模块,接口可以使得模块之间的耦合降低。

掌握桥梁模式的正确使用收益的不只是你,还有那些负责维护你代码的人。把抽象于其实现隔离开,可独立地管理软件的各个部分,bug也因此更容易查找。

桥梁模式目的就是让API更加健壮,提高组件的模块化程度,促成更简洁的实现,并提高抽象的灵活性。一个好的示例:

  1. element.onclick = function(){ //API可控制性提高了,使得这个API更加健壮  
  2.      new someFunction(element,param,callback);  
  3.   } 

注:桥梁模式还可以用于连接公开的API代码和私有的实现代码,还可以把多个类连接在一起。在文章封装介绍的部分提到过特权方法,也是桥梁模式的一种特例。《JS设计模式》上找的示例,加深大家对这个模式的理解:

  1. //错误的方式  
  2.  2  //这个API根据事件监听器回调函数的工作机制,事件对象被作为参数传递给这个函数。本例中并没有使用这个参数,而只是从this对象获取ID。  
  3.  3  addEvent(element,'click',getBeerById);  
  4.  4  function(e){  
  5.  5     var id = this.id;  
  6.  6     asyncRequest('GET','beer.url?id=' + id,function(resp){  
  7.  7        //Callback response  
  8.  8        console.log('Requested Beer: ' + resp.responseText);  
  9.  9     });  
  10. 10  }  
  11. 11   
  12. 12  //好的方式  
  13. 13  //从逻辑上分析,把id传给getBeerById函数式合情理的,且回应结果总是通过一个毁掉函数返回。这么理解,我们现在做的是针对接口而不是实现进行编程,用桥梁模式把抽象隔离开来。  
  14. 14  function getBeerById(id,callback){  
  15. 15     asyncRequest('GET','beer.url?id=' + id,function(resp){  
  16. 16        callback(resp.responseText)  
  17. 17     });  
  18. 18  }  
  19. 19  addEvent(element,'click',getBeerByIdBridge);  
  20. 20  function getBeerByIdBridge(e){  
  21. 21     getBeerById(this.id,function(beer){  
  22. 22        console.log('Requested Beer: ' + beer);  
  23. 23     });  
  24. 24  }  

装饰者(Decorator)模式:这个模式就是为对象增加功能(或方法)。

动态地给一个对象添加一些额外的职责。就扩展功能而言,它比生成子类方式更为灵活。

装饰者模式和组合模式有很多共同点,它们都与所包装的对象实现统一的接口并且会把任何方法条用传递给这些对象。可是组合模式用于把众多子对象组织为一个整体,而装饰者模式用于在不修改现有对象或从派生子类的前提下为其添加方法。

装饰者的运作过程是透明的,这就是说你可以用它包装其他对象,然后继续按之前使用那么对象的方法来使用,从下面的例子中就可以看出。还是从代码中理解吧:

  1.  1  //创建一个命名空间为myText.Decorations  
  2.  2  var myText= {};  
  3.  3  myText.Decorations={};  
  4.  4  myText.Core=function(myString){  
  5.  5     this.show = function(){return myString;}  
  6.  6  }  
  7.  7  //第一次装饰  
  8.  8  myText.Decorations.addQuestuibMark =function(myString){  
  9.  9     this.show = function(){return myString.show()+'?';};  
  10. 10  }  
  11. 11  //第二次装饰  
  12. 12  myText.Decorations.makeItalic = function(myString){  
  13. 13     this.show = function(){return '<li>'+myString.show()+'</li>'};  
  14. 14  }  
  15. 15  //得到myText.Core的实例  
  16. 16  var theString = new myText.Core('this is a sample test String');  
  17. 17  alert(theString.show()); //output 'this is a sample test String'  
  18. 18  theString = new myText.Decorations.addQuestuibMark(theString);  
  19. 19  alert(theString.show()); //output 'this is a sample test String?'  
  20. 20  theString = new myText.Decorations.makeItalic (theString);  
  21. 21  alert(theString.show()); //output '<li>this is a sample test String</li>'  
  22.  

从这个示例中可以看出,这一切都可以不用事先知道组件对象的接口,甚至可以动态的实现,在为现有对象增添特性这方面,装饰者模式有极大的灵活性。

如果需要为类增加特性或者方法,而从该类派生子类的解决办法并不实际的话,就应该使用装饰者模式。派生子类之所以会不实际最常见的原因是需要添加的特性或方法的数量要求使用大量子类。

组合(Composite)模式:将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。

组合模式是一种专为创建Web上的动态用户界面而量身定制的模式。使用这种模式,可以用一条命令在多个对象上激发复杂的或递归的行为。组合模式擅长于对大批对象进行操作。

组合模式的好处:1.程序员可以用同样的方法处理对象的集合与其中的特定子对象;2.它可以用来把一批子对象组织成树形结构,并且使整棵树都可被便利。

组合模式适用范围:1.存在一批组织成某处层次体系的对象(具体结构可能在开发期间无法知道);2.希望对这批对象或其中的一部分对象实话一个操作。

其实组合模式就是将一系列相似或相近的对象组合在一个大的对象,由这个大对象提供一些常用的接口来对这些小对象进行操作,代码可重用,对外操作简单。例如:对form内的元素,不考虑页面设计的情况下,一般就剩下input了,对于这些input都有name和value的属性,因此可以将这些input元素作为form对象的成员组合起来,form对象提供对外的接口,便可以实现一些简单的操作,比如设置某个input的value,添加/删除某个input等等。

这种模式描述起来比较吃力,我从《JS设计模式》上找个一个实例,大家还是看代码吧:先创建组合对象类

  1. 1  // DynamicGallery Class  
  2.  2  var DynamicGallery = function (id) { // 实现Composite,GalleryItem组合对象类   
  3.  3     this.children = [];  
  4.  4     this.element = document.createElement('div');  
  5.  5     this.element.id = id;  
  6.  6     this.element.className = 'dynamic-gallery';  
  7.  7  }  
  8.  8  DynamicGallery.prototype = {  
  9.  9     // 实现Composite组合对象接口   
  10. 10     add: function (child) {  
  11. 11        this.children.push(child);  
  12. 12        this.element.appendChild(child.getElement());  
  13. 13     },  
  14. 14     remove: function (child) {  
  15. 15        for (var node, i = 0; node = this.getChild(i); i++) {  
  16. 16           if (node == child) {  
  17. 17              this.children.splice(i, 1);  
  18. 18              break;  
  19. 19           }  
  20. 20        }  
  21. 21        this.element.removeChild(child.getElement());  
  22. 22     },  
  23. 23     getChild: function (i) {  
  24. 24        return this.children[i];  
  25. 25     },  
  26. 26     // 实现DynamicGallery组合对象接口   
  27. 27     hide: function () {  
  28. 28        for (var node, i = 0; node = this.getChild(i); i++) {  
  29. 29           node.hide();  
  30. 30        }  
  31. 31        this.element.style.display = 'none';  
  32. 32     },  
  33. 33     show: function () {  
  34. 34        this.element.style.display = 'block';  
  35. 35        for (var node, i = 0; node = getChild(i); i++) {  
  36. 36           node.show();  
  37. 37        }  
  38. 38     },  
  39. 39     // 帮助方法   
  40. 40     getElement: function () {  
  41. 41        return this.element;  
  42. 42     }  
  43. 43  } 

再创建叶对象类

  1. 1  var GalleryImage = function (src) { // 实现Composite和GalleryItem组合对象中所定义的方法   
  2.  2     this.element = document.createElement('img');  
  3.  3     this.element.className = 'gallery-image';  
  4.  4     this.element.src = src;  
  5.  5  }  
  6.  6  GalleryImage.prototype = {  
  7.  7     // 实现Composite接口   
  8.  8     // 这些是叶结点,所以我们不用实现这些方法,我们只需要定义即可   
  9.  9     add: function () { },  
  10. 10     remove: function () { },  
  11. 11     getChild: function () { },  
  12. 12     // 实现GalleryItem接口   
  13. 13     hide: function () {  
  14. 14        this.element.style.display = 'none';  
  15. 15     },  
  16. 16     show: function () {  
  17. 17        this.element.style.display = '';  
  18. 18     },  
  19. 19     // 帮助方法   
  20. 20     getElement: function () {  
  21. 21        return this.element;  
  22. 22     }  
  23. 23  }  

现在我们可以使用这两个类来管理图片:

  1.  1  var topGallery = new DynamicGallery('top-gallery');   
  2.  2  topGallery.add(new GalleryImage('/img/image-1.jpg'));   
  3.  3  topGallery.add(new GalleryImage('/img/image-2.jpg'));   
  4.  4  topGallery.add(new GalleryImage('/img/image-3.jpg'));   
  5.  5  var vacationPhotos = new DyamicGallery('vacation-photos');   
  6.  6  for(var i = 0, i < 30; i++){   
  7.  7  vacationPhotos.add(new GalleryImage('/img/vac/image-' + i + '.jpg'));   
  8.  8  }   
  9.  9  topGallery.add(vacationPhotos);   
  10. 10  topGallery.show();   
  11. 11  vacationPhotos.hide();   
  12.  

门面(facade)模式:门面模式是几乎所有JavaScript库的核心原则

子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用,简单的说这是一种组织性的模式,它可以用来修改类和对象的接口,使其更便于使用。

门面模式的两个作用:1.简化类的接口;2.消除类与使用它的客户代码之间的耦合。

门面模式的使用目的就是图方面。

想象一下计算机桌面上的那些快捷方式图标,它们就是在扮演一个把用户引导至某个地方的接口的角色,每次操作都是间接的执行一些幕后的命令。

你在看这篇的博客的时候我就假设你已经有JavaScript的使用经验了,那么你一定写过或者看过这样的代码:

  1. 1  var addEvent = function(el,type,fn){  
  2. 2     if(window.addEventListener){  
  3. 3        el.addEventListener(type,fn);  
  4. 4     }else if(window.attachEvent){  
  5. 5        el.attachEvent('on'+type,fn);  
  6. 6     }else{  
  7. 7        el['on'+type] = fn;  
  8. 8     }  
  9. 9  }  
  10.  

这个就是一个JavaScript中常见的事件监听器函数,这个函数就是一个基本的门面,有了它,就有了为DOM节点添加事件监听器的简便方法。

现在要说门面模式的精华部分了,为什么说JavaScript库几乎都会用这种模式类。假如现在要设计一个库,那么最好把其中所有的工具元素放在一起,这样更好用,访问起来更简便。看代码:

  1.  1  //_model.util是一个命名空间  
  2.  2  _myModel.util.Event = {  
  3.  3     getEvent:function(e){  
  4.  4        return e|| window.event;  
  5.  5     },  
  6.  6     getTarget:function(e){  
  7.  7        return e.target||e.srcElement;  
  8.  8     },  
  9.  9     preventDefault:function(e){  
  10. 10        if(e.preventDefault){  
  11. 11           e.preventDefault();  
  12. 12        }else{  
  13. 13           e.returnValue = false;  
  14. 14        }  
  15. 15     }  
  16. 16  };  
  17. 17  //事件工具大概就是这么一个套路,然后结合addEvent函数使用  
  18. 18  addEvent(document.getElementsByTagName('body')[0],'click',function(e){  
  19. 19     alert(_myModel.util.Event.getTarget(e));  
  20. 20  });  
  21.  

个人认为,在处理游览器差异问题时最好的解决办法就是把这些差异抽取的门面方法中,这样可以提供一个更一致的接口,addEvent函数就是一个例子。


适配置器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作,使用这种模式的对象又叫包装器,因为他们是在用一个新的接口包装另一个对象。

从表面上看,它和门面模式有点相似,差别在于它们如何改变接口,门面模式展现的是一个简化的接口,它并不提供额外的选择,而适配器模式则要把一个接口转换为另一个接口,它并不会滤除某些能力,也不会简化接口。先来一个简单的示例看看:

  1.  1  //假如有一个3个字符串参数的函数,但是现在拥有的却是一个包含三个字符串元素的对象,那么就可以用一个配置器来衔接二者  
  2.  2  var clientObject = {  
  3.  3     str1:'bat',  
  4.  4     str2:'foo',  
  5.  5     str3:'baz'  
  6.  6  }  
  7.  7  function interfaceMethod(str1,str2,str3){  
  8.  8  alert(str1)  
  9.  9  }  
  10. 10  //配置器函数  
  11. 11  function adapterMethod(o){  
  12. 12     interfaceMethod(o.str1, o.str2, o.str3);  
  13. 13  }  
  14. 14  adapterMethod(clientObject)  
  15. 15  //adapterMethod函数的作为就在于对interfaceMethod函数进行包装,并把传递给它的参数转换为后者需要的形式。  
  16.  

适配器模式的工作机制是:用一个新的接口对现有类得接口进行包装。

示例:适配两个库。下面的例子要实现的是从Prototype库的$函数到YUI的get方法的转换。

  1. 1  //先看它们在接口方面的差别  
  2. 2  //Prototype $ function   
  3. 3  function $(){  
  4. 4  var elements = new Array();  
  5. 5  for(var i=0;i<arguments.length;i++){  
  6. 6  var element = arguments[i];  
  7. 7  if(typeof element == 'string'){  
  8. 8  element = document.getElementById(element);  
  9. 9  }  
  10. 10  if(typeof.length ==1) return element;  
  11. 11  elements.push(element);  
  12. 12  }  
  13. 13  return elements;  
  14. 14  }  
  15. 15  //YUI get method  
  16. 16  YAHOO.util.Dom.get = function(el){  
  17. 17  if(YAHOO.lang.isString(el)){  
  18. 18  return document.getElementById(el);  
  19. 19  }  
  20. 20  if(YAHOO.lang.isArray(el)){  
  21. 21  var c =[];  
  22. 22  for(var i=0,len=el.length;i<len;++i){  
  23. 23  c[c.length] = YAHOO.util.Dom.get(el[i]);  
  24. 24  }  
  25. 25  return c;  
  26. 26  }  
  27. 27  if(el){  
  28. 28  return el;  
  29. 29  }  
  30. 30  return null;  
  31. 31  }  
  32. 32  //二者区别就在于get具有一个参数,且可以是HTML,字符串或者数组;而$木有正是的参数,允许使用者传入任意数目的参数,不管HTML还是字符串。  
  33. 33  //如果需要从使用Prototype的$函数改为使用YUI的get方法(或者相反,那么用适配器模式其实很简单)  
  34. 34  function PrototypeToYUIAdapter(){  
  35. 35  return YAHOO.util.Dom.get(arguments);  
  36. 36  }  
  37. 37  function YUIToPrototypeAdapter(el){  
  38. 38  return $.apply(window,el instanceof Array?el:[el]);  
  39. 39  } 

享元(Flyweight)模式:运用共享技术有效地支持大量细粒度的对象。

享元模式可以避免大量非常相似类的开销。在程序设计中有时需要生成大量细粒度的类实例来表示数据。如果发现这些实例除了几个参数外基本伤都是相同的,有时就能够受大幅度第减少需要实例化的类的数量。如果能把这些参数移到类实例外面,在方法调用时将他们传递进来,就可以通过共享大幅度地减少单个实例的数目。

从实际出发说说自己的理解吧。

  1. 1  组成部分  
  2.  2  “享元”:抽离出来的外部操作和数据;  
  3.  3  “工厂”:创造对象的工厂;  
  4.  4  “存储器”:存储实例对象的对象或数组,供“享元”来统一控制和管理。  
  5.  5   
  6.  6  应用场景  
  7.  7  1. 页面存在大量资源密集型对象;  
  8.  8  2. 这些对象具备一定的共性,可以抽离出公用的操作和数据  
  9.  9   
  10. 10  关键  
  11. 11  1. 合理划分内部和外部数据。  
  12. 12  既要保持每个对象的模块性、保证享元的独立、可维护,又要尽可能多的抽离外部数据。  
  13. 13  2. 管理所有实例  
  14. 14  既然抽离出了外部数据和操作,那享元就必须可以访问和控制实例对象。在JavaScript这种动态语言中,这个需求是很容易实现的:我们可以把工厂生产出的对象简单的扔在一个数组中。为每个对象设计暴露给外部的方法,便于享元的控制。  
  15. 15   
  16. 16  优点  
  17. 17  1. 将能耗大的操作抽离成一个,在资源密集型系统中,可大大减少资源和内存占用;  
  18. 18  2. 职责封装,这些操作独立修改和维护;  
  19. 19   
  20. 20  缺点  
  21. 21  1. 增加了实现复杂度。  
  22. 22  将原本由一个工厂方法实现的功能,修改为了一个享元+一个工厂+一个存储器。  
  23. 23  2. 对象数量少的情况,可能会增大系统开销。 

示例:

  1.  1  //汽车登记示例  
  2.  2  var Car = function(make,model,year,owner,tag,renewDate){  
  3.  3  this.make=make;  
  4.  4  this.model=model;  
  5.  5  this.year=year;  
  6.  6  this.owner=owner;  
  7.  7  this.tag=tag;  
  8.  8  this.renewDate=renewDate;  
  9.  9  }  
  10. 10  Car.prototype = {  
  11. 11  getMake:function(){  
  12. 12  return this.make;  
  13. 13  },  
  14. 14  getModel:function(){  
  15. 15  return this.model;  
  16. 16  },  
  17. 17  getYear:function(){  
  18. 18  return this.year;  
  19. 19  },  
  20. 20  transferOwner:function(owner,tag,renewDate){  
  21. 21  this.owner=owner;  
  22. 22  this.tag=tag;  
  23. 23  this.renewDate=renewDate;  
  24. 24  },  
  25. 25  renewRegistration:function(renewDate){  
  26. 26  this.renewDate=renewDate;  
  27. 27  }  
  28. 28  }  
  29. 29  //数据量小到没多大的影响,数据量大的时候对计算机内存会产生压力,下面介绍享元模式优化后  
  30. 30  //包含核心数据的Car类  
  31. 31  var Car=function(make,model,year){  
  32. 32  this.make=make;  
  33. 33  this.model=model;  
  34. 34  this.year=year;  
  35. 35  }  
  36. 36  Car.prototype={  
  37. 37  getMake:function(){  
  38. 38  return this.make;  
  39. 39  },  
  40. 40  getModel:function(){  
  41. 41  return this.model;  
  42. 42  },  
  43. 43  getYear:function(){  
  44. 44  return this.year;  
  45. 45  }  
  46. 46  }  
  47. 47  //中间对象,用来实例化Car类  
  48. 48  var CarFactory=(function(){  
  49. 49  var createdCars = {};  
  50. 50  return {  
  51. 51  createCar:function(make,model,year){  
  52. 52  var car=createdCars[make+"-"+model+"-"+year];  
  53. 53  return car ? car : createdCars[make + '-' + model + '-' + year] =(new Car(make,model,year));  
  54. 54  }  
  55. 55  }  
  56. 56  })();  
  57. 57  //数据工厂,用来处理Car的实例化和整合附加数据  
  58. 58  var CarRecordManager = (function() {  
  59. 59  var carRecordDatabase = {};  
  60. 60  return {  
  61. 61  addCarRecord:function(make,model,year,owner,tag,renewDate){  
  62. 62  var car = CarFactory.createCar(make, model, year);  
  63. 63  carRecordDatabase[tag]={  
  64. 64  owner:owner,  
  65. 65  tag:tag,  
  66. 66  renewDate:renewDate,  
  67. 67  car:car  
  68. 68  }  
  69. 69  },  
  70. 70  transferOwnership:function(tag, newOwner, newTag, newRenewDate){  
  71. 71  var record=carRecordDatabase[tag];  
  72. 72  record.owner = newOwner;  
  73. 73  record.tag = newTag;  
  74. 74  record.renewDate = newRenewDate;  
  75. 75  },  
  76. 76  renewRegistration:function(tag,newRenewDate){  
  77. 77  carRecordDatabase[tag].renewDate=newRenewDate;  
  78. 78  },  
  79. 79  getCarInfo:function(tag){  
  80. 80  return carRecordDatabase[tag];  
  81. 81  }  
  82. 82  }  
  83. 83  })();  
  84.  

代理(Proxy)模式:此模式最基本的形式是对访问进行控制。代理对象和另一个对象(本体)实现的是同样的接口,可是实际上工作还是本体在做,它才是负责执行所分派的任务的那个对象或类,代理对象不会在另以对象的基础上修改任何方法,也不会简化那个对象的接口。

举一个具体的情况:如果那个对象在某个远端服务器上,直接操作这个对象因为网络速度原因可能比较慢,那我们可以先用Proxy来代替那个对象。

总之对于开销较大的对象,只有在使用它时才创建,这个原则可以为我们节省很多内存。《JS设计模式》上的图书馆示例:

  1. 1  var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor', 'setAuthor', 'display']);  
  2.  2  var Book = function(isbn, title, author) {  
  3.  3      //...  
  4.  4  }   
  5.  5  // implements Publication  
  6.  6  implements(Book,Publication);  
  7.  7   
  8.  8  /* Library interface. */  
  9.  9  var Library = new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']);  
  10. 10   
  11. 11  /* PublicLibrary class. */  
  12. 12  var PublicLibrary = function(books) {  
  13. 13      //...  
  14. 14  };  
  15. 15  // implements Library  
  16. 16  implements(PublicLibrary,Library);   
  17. 17   
  18. 18  PublicLibrary.prototype = {  
  19. 19      findBooks: function(searchString) {  
  20. 20         //...  
  21. 21      },  
  22. 22      checkoutBook: function(book) {  
  23. 23          //...  
  24. 24      },  
  25. 25      returnBook: function(book) {  
  26. 26          //...  
  27. 27      }  
  28. 28  };  
  29. 29   
  30. 30  /* PublicLibraryProxy class, a useless proxy. */  
  31. 31  var PublicLibraryProxy = function(catalog) {   
  32. 32      this.library = new PublicLibrary(catalog);  
  33. 33  };  
  34. 34  // implements Library  
  35. 35  implements(PublicLibraryProxy,Library);  
  36. 36   
  37. 37  PublicLibraryProxy.prototype = {  
  38. 38      findBooks: function(searchString) {  
  39. 39          return this.library.findBooks(searchString);  
  40. 40      },  
  41. 41      checkoutBook: function(book) {  
  42. 42          return this.library.checkoutBook(book);  
  43. 43      },  
  44. 44      returnBook: function(book) {  
  45. 45          return this.library.returnBook(book);  
  46. 46      }  
  47. 47  }; 

观察者(Observer)模式:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

观察者模式中存在两个角色,观察者和被观察者。在DOM的编程环境中的高级事件模式中,事件监听器说到底就是一种内置的观察者。事件处理器(handler)和时间监听器(listener)并不是一回事,前者就是一种把事件传给与其关联的函数的手段,而在后者中,一个时间可以与几个监听器关联,每个监听器都能独立于其他监听器而改变。

  1.  1  //使用时间监听器可以让多个函数相应一个事件  
  2.  2  var fn1 = function(){  
  3.  3     //code  
  4.  4  }  
  5.  5  var fn2 = function(){  
  6.  6     //code  
  7.  7  }  
  8.  8  addEvent(element,'click',fn1);  
  9.  9  addEvent(element,'click',fn2)  
  10. 10   
  11. 11  //而时间处理函数就办不到  
  12. 12  element.onclick = fn1;  
  13. 13  element.onclick = fn2;  
  14.  

观察者模式是开发基于行为的应用程序的有力手段,前端程序员可做的就是借助一个事件监听器替你处理各种行为,从而降低内存消耗和提高互动性能。

命令(Command)模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。

命令对象是一个操作和用来调用这个操作的对象的结合体,所有的命名对象都有一个执行操作,其用途就是调用命令对象所绑定的操作。示例:

  1.  1  car Calculator={  
  2.  2     add:function(x,y){  
  3.  3        return x+y;  
  4.  4     },  
  5.  5     substract:function(x,y){  
  6.  6        return x-y;  
  7.  7     },  
  8.  8     multiply:function(x,y){  
  9.  9        return x*y;  
  10. 10     },  
  11. 11     divide:function(x,y){  
  12. 12        return x/y;  
  13. 13     }  
  14. 14  }  
  15. 15  Calculator.calc = function(command){  
  16. 16     return Calculator[command.type](command.op1,command.opd2)  
  17. 17  };  
  18. 18  Calculator.calc({type:'add',op1:1,op2:1});  
  19. 19  Calculator.calc({type:'substract',op1:5,op2:2});  
  20. 20  Calculator.calc({type:'multiply',op1:5,op2:2});  
  21. 21  Calculator.calc({type:'divide',op1:8,op2:4});  
  22.  

命名模式的主要用途是把调用对象(用户界面,API和代理等)与实现操作的对象隔离开,也就是说使对象间的互动方式需要更高的模块化时都可以用到这种模式。

职责链(Chain Of Responsibility)模式:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。

职责链由多个不同类型的对象组成:发送者是发出请求的对象,而接收者则是接收请求并且对其进行处理或传递的对象,请求本身有时也是一个对象,它封装着与操作有关的所有数据。

典型的流程大致是:

1.发送者知道链中第一个接收者,它向这个接收者发出请求。

2.每一个接收者都对请求进行分析,然后要么处理它,要么将其往下传。

3.每一个接收者知道的其他对象只有一个,即它在链中的下家。

4.如果没有任何接收者处理请求,那么请求将从链上离开,不同的实现对此也有不同的反应,一般会抛出一个错误。

职责链模式的适用范围:1.有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定;2.想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;3.可处理一个请求的对象集合需要被动态指定。

确实对这种模式不了解,相关资料也较少,所以代码先不上了。看看大家对这个模式有木有什么好的理解或者能较好表达这种模式的代码,谢谢了。

结束语:

1.每种模式都有自己的优缺点,所以每种模式的正确使用还得看开发人员本身的功力;

2.就算不使用JavaScript设计模式一样可以写出 复杂的可使用的代码,可是如果你想真正了解JavaScript面向对象能力,学习提高代码的模块化程度﹑可维护性﹑可靠性和效率,那么合理的运用JavaScript设计模式将会是一个优秀 前端开发攻城湿 必备的能力。

最后,祝大家工作顺利。


前端面试的话术集锦第 21 篇博文——高频考点(设计模式) 工厂模式分为简单工厂、抽象工厂、工厂方法模式;单例模式很常用,比如全局缓存、全局状态管理等等这些只需要一个对象,就可以使用单例模式;适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作;装饰模式不需要改变已有的接口,作用是给对象添加功能;代理模式是为了控制对对象的访问,不让外部直接访问到对象;发布-订阅模式也叫做观察者模式,通过一对一或者一对多的依赖关系,当对象发生改变时,订阅方都会收到通知。外观模式提供了一个接口,隐藏了内部的逻辑,更加方便外部调用。 阅读详情

相关推荐

LTE信令流程实战:从Attach到Service Request的完整解析(附常见问题排查)

本文深入解析LTE信令流程中的Attach和Service Request关键环节,提供从基础原理到实战排查的完整指南。涵盖鉴权、承载建立等核心步骤,分析常见故障模式及优化方案,并附赠信令跟踪工具使用技巧,助力通信工程师提升网络运维效率。

alpha的博客 319

盘点JavaScript设计模式(常用十五大设计模式)

javaScript设计模式前言一、设计原则单一职责原则(SRP)最少知识原则(LKP)开放-封闭原则(OCP)里氏替换原则 The Liskov Substitution Principle (LSP)接口分离原则 The Interface Segregation Principle (ISP)依赖倒置原则 The Dependency-Inversion Principle (DIP)二、JavaScript设计模式1、单例模式2、策略模式3、代理模式4、迭代器模式5、发布-订阅模式6、命令模式7、组

qq_41311259的博客 3653

Android逆向实战:通过smali/baksmali动态注入DEX逻辑

本文详细介绍了在无源码条件下对Android应用进行动态逻辑注入的实战方法。通过使用smali和baksmali工具对DEX文件进行反编译、修改与重打包,实现在目标方法中插入自定义代码(如日志打印),并分享了定位目标、处理寄存器、避免常见错误的实用技巧与避坑指南。

weixin_29008691的博客 364

js设计模式-常见的13种

单例模式 (Singleton Pattern):单例模式用于限制一个类只有一个实例,并提供一个全局访问点; 工厂模式 (Factory Pattern):工厂模式通过使用工厂方法创建对象,而不是直接调用构造函数来创建对象

一步一个脚印,踏实努力,向着目标前进 2769

C++ muduo网络库知识分享01 - Linux平台下muduo网络库源码编译安装

Muduo is a multithreaded C++ network library based on the reactor pattern. muduo库的介绍就是:一个基于reactor反应堆模型的多线程C++网络库。 muduo网络库是C++语言开发的一个非常优秀的网络库,作者陈硕,muduo网络库在多线程环境下性能非常高,该系列文章会分享一些muduo库源码的知识,包括它的线程池源码...

大秦坑王的专栏 3万+

从零到一:NI USB-8506 LIN通讯的驱动安装与硬件调试全攻略

本文详细介绍了NI USB-8506 LIN通讯设备的驱动安装与硬件调试全流程,包括硬件准备、驱动下载与配置、NI MAX设置及常见问题解决方案。通过实战应用与性能优化技巧,帮助用户快速掌握LIN通讯技术,适用于车载数据记录和自动化测试等场景。

weixin_28273593的博客 924

javaScript之设计模式

JavaScript设计模式是一种在JavaScript开发中常用的编程模式,用于解决特定问题的代码组织和架构。通过使用设计模式,可以提高代码的可读性、可维护性和可扩展性。以下是几种常见的JavaScript设计模式:单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。工厂模式(Factory Pattern):根据参数的不同,创建不同的对象实例,隐藏具体的实现细节。

m0_74242675的博客 1418

TMS320F28377D的sys/bios双核工程配置详解——从零搭建到RAM优化

本文详细解析了TMS320F28377D双核系统在sys/bios环境下的工程配置与RAM优化策略。从开发环境搭建、SYS/BIOS工程创建到双核CMD文件配置,提供了实战技巧和常见问题解决方案,帮助开发者高效实现电力电子控制系统中的双核协作与性能优化。

weixin_28292761的博客 600

JavaScript中常见的十五种设计模式

一、单例模式 二、策略模式 三、代理模式 四、迭代器模式 五、发布—订阅模式 六、命令模式 七、组合模式 八、模板方法模式 九、享元模式 十、职责链模式 十一、中介者模式 十二、装饰者模式 十三、状态模式 十四、适配器模式 十五、外观模式

chenzoff的专栏 5393

RT-DETR-R18 的轻量化 Trick:剪枝与量化在模型中的应用

RT-DETR-R18轻量化技术通过结构化剪枝、混合精度量化和知识蒸馏的组合优化,实现了显著模型压缩效果。摘要如下: RT-DETR-R18模型通过创新轻量化技术组合,在保持46.2% mAP精度的前提下,将模型大小压缩至2.1MB(压缩率78%),计算量降至3.2 GFLOPs(减少78.2%),实现移动端28ms实时推理(加速37.8%)。核心技术包括:1)渐进式结构化剪枝系统,通过L1范数重要性评估和动态剪枝调度;2)混合精度量化策略,对关键层保留FP16精度;3)教师-学生知识蒸馏框架保持模型表达能

走向CTO的路上... 1191

【项目典型案例】-2-任意帧/特定帧CAN唤醒对ECU首帧/启动时间的影响

任意帧/特定帧CAN唤醒对ECU启动时间的影响

「汽车电子助手」的博客 1943

java开发论文答辩_毕业设计答辩基于JavaWeb的博客网站的设计与实现.ppt

毕业设计答辩基于JavaWeb的博客网站的设计与实现博客网站的设计与实现——指导老师 XXXXX 学生 XXXXX毕业答辩一、研究背景和内容二、系统环境的配置三、系统使用的技术四、系统概述五、特色与创新六、下阶段研究内容七、致谢第一批中文博客是在2002年出现的,早些时候博客只是一个新的网络名词并无特殊之处,就在不经意间,博客竟如雨后春笋般冒出来,博得越来越多网民的欢心,并在互联网上...

weixin_29901323的博客 1704

第六:Charles抓包-苹果手机抓包

【代码】第六:Charles抓包-苹果手机抓包。

欢迎来到本博客! 1658

随笔目录【2016年12月1日整理中~】

.Net 记录(9) 不用写Windows服务实现定时器功能(FluentScheduler )自定义log日志iis 7.5应用程序池自动停止集合已修改;可能无法执行枚举操作。ADO.NET – 3.书籍管理系统详解ADO.NET - 2.调用存储过程ADO.NET - 1.基础(SqlCommand\ExecuteScalar\ExecuteReader\sqlDataAdapter)C# ...

weixin_34405925的博客 339

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Python毕设项目:基于 Flask 框架的个人博客平台 博文点赞收藏评论一体化管理系统 (源码+文档,讲解、调试运行,定制等)

主要内容:免费开题报告、任务书、全bao定制+中期检查PPT、代码编写、🚢文编写和辅导、🚢文降重、长期答辩答疑辅导、一对一专业代码讲解辅导答辩、模拟答辩演练、和理解代码逻辑思路。

2501_91703032的博客 9

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

JS-Javascript 迭代器设计模式 Iterator pattern

这篇博文简短的提一下Iterator的设计模式,以及这种设计模式给我们带来什么样的好处。1.begin为了体现面向对象的特性(近年来的ES6为这方面做了很大的努力),此篇博文的语言编写基于typescript,当然也会附上原生js的书写方式。1.1迭代器给我们带来了什么样的好处迭代器可以使我们快速的遍历一个序列对象,一个良好的迭代器使我们不用了解该序列底层的结构,而只用关心拿到值之后的处理,并且迭代

小辣抓 2499

javascript设计模式【下】

参考《javascript设计模式》[美]Addy Osmani一书,下面介绍使用javascript经常会使用的主要设计模式。本博文是使用ES5语法的【下】篇,还有一个【上】篇,ES6语法会单独写个博客。 主要是以下几个设计模式: Constructor Pattern 构造模式 Module Pattern 模块化模式 Revealing Module Pattern 揭露模块化模式 Sin

前端-后端学习之路 1153

javascript设计模式【上】

参考《javascript设计模式》[美]Addy Osmani一书,下面介绍使用javascript经常会使用的主要设计模式。本博文是使用ES5语法的【上】篇,还有一个【下】篇,ES6语法会单独写个博客。 主要是以下几个设计模式: Constructor Pattern 构造模式 Module Pattern 模块化模式 Revealing Module Pattern 揭露模块化模式 Sin

前端-后端学习之路 2186

JavaScript 设计模式 —— 单例模式

在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案 —— 设计模式 设计模式原则:“找出程序中变化的地方,并将变化封装起来”

weixin_46653793的博客 1236

Js-JavaScript 观察者设计模式 Observer Pattern

在函数式编程中,观察者设计模式是非常有必要的,尽管在JS里到处都充斥着观察者模式(Observer Pattern),但我们仍然有必要去了解它。以便实现自己的观察者模式,用于更复杂的应用场景。其实要实现观察者模式实在是很简单。 现在我们回顾一下我们遇到过的观察者模式,最常见的应该是在前端领域中的DOM事件监听。JS允许我们用两种形式绑定DOM事件,一种为钩子捕获,另一种为事件监听,今天我们主要的关

小辣抓 2975

javascript设计模式 使用ES6语法

参考《javascript设计模式》[美]Addy Osmani一书,下面介绍使用javascript经常会使用的主要设计模式。本博文为ES6语法的博客,还有使用ES5语法的【上】【下】两篇。 主要是以下几个设计模式: Constructor Pattern 构造模式 Module Pattern 模块化模式 Revealing Module Pattern 揭露模块化模式 Singleton

前端-后端学习之路 6478

【JavaScript设计模式】(一)

最近在学习JavaScript设计模式,学习过之后感觉很有收获,现把自己的学习内容总结如下: 模式:是一种可服用的解决方案,可用于解决软件设计中遇到的常见问题。 设计模式:有三大好处,模式是已经验证的解决方案;模式很容易被复用;模式富有表达力。 本篇主要介绍8种设计模式,其余模式将在后续博文中继续介绍

季诗筱的博客 4834

javascript的设计模式(一)———— 多态

从事前端有两年的时间了,一直觉得自己只致力于实践而忽视了理论上的知识,所以现在想边看《javascript设计模式与开发实践》这本书的同时边记录下自己的学习与理解。刚开始写自己的博文,嗯。。。不怎么会组织文字。。。还是快点开始正文吧^^ 正文 What is 设计模式? 设计模式其主要思想就是面向对象编程,即不在乎你是谁(is-A),而在于你可以做什么(Has-A)。就像你不必知道电视机...

djex8531010的博客 152

JavaScript设计模式——Observe模式(观察者模式)

通常一个优秀的项目,会使用到很多的设计模式,这些设计模式在我们的解决方案中,会起到十分重要的作用,它的存在,能使项目的结构更加简洁、清晰、易于理解。 所以了解常用的设计模式,会让我们在日常开发更加得心应手,也会让团队沟通变得更加顺畅。 在《设计模式:可复用面向对象软件基础》一书中强调:当我们评估一个面向对象系统的质量时,所使用的方法之一就是判断系统的设计者是否强调了对象之间的公共系统关系。 简介 那么在当前这个博文中,我介绍的就是 JavaScript 语言的 Observer设计模式。 初次接触 Ob.

Vico的Hu的博客 2237

JS设计模式——构造器模式

引入 看过高程书的对这个设计模式应该很熟悉。构造器模式属于创建型设计模式。那么什么是创建型设计模式呢? 创建型设计模式专注于处理对象的创建机制。也就是说这类模式,都和创建对象的过程密不可分。 除了构造器模式属于这类模式。还有其他一些设计模式也属于创建型设计模式。比如: 工厂模式 抽象模式 原型模式 单例模式 生成器模式 下面还是先来谈谈构造器模式。 什么是构造器? 在面向对象的编程语言中,构造器...

yh95926的博客 714

JavaScript学习书籍推荐(步步为营)

概述 本文只要介绍,JavaScript学习的书籍。 《JavaScript之美》 《JavaScript设计模式》 《JavaScript框架设计(第2版)》 《JavaScript开发框架权威指南》 《JavaScript核心技术开发解密》 《JavaScript函数式编程指南》 《JavaScript函数式编程思想》 《JavaScript从入门到精通》 《JavaScriptES6函数式编程入门经典(Web开发经典丛书)》 《HTML5+CSS3+JavaSc...

qq_36075612的博客 6987
上一篇: Java 1.7 里有什么?
下一篇: 一个js游戏引擎,没完成呢还,自己写的
程序员迈克
博客等级 码龄16年 36粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值