http://sd.csdn.net/a/20110902/303983.html
前言:
JavaScript设计模式的作用——提高代码的重用性,可读性,使代码更容易的维护和扩展。
1.单体模式,工厂模式,桥梁模式个人认为这个一个优秀前端必须掌握的模式,对抽象编程和接口编程都非常有好处。
2.装饰者模式和组合模式有很多相似的地方,它们都与所包装的对象实现同样的接口并且会把任何方法的调用传递给这些对象。装饰者模式和组合模式是本人描述的较吃力的两个模式,我个人其实也没用过,所以查了很多相关资料和文档,请大家海涵。
3.门面模式是个非常有意思的模式,几乎所有的JavaScript库都会用到这个模式,假如你有逆向思维或者逆向编程的经验,你会更容易理解这个模式(听起来有挑战,其实一接触你就知道这是个很简单的模式);还有配置器模式得和门面模式一块拿来说,这个模式对现有接口进行包装,合理运用可以很多程度上提高开发效率。这两个模式有相似的地方,所以一块理解的话相信都会很快上手的。
4.享元模式是一种以优化为目的的模式。
5.代理模式主要用于控制对象的访问,包括推迟对其创建需要耗用大量计算资源的类得实例化。
6.观察者模式用于对对象的状态进行观察,并且当它发生变化时能得到通知的方法。用于让对象对事件进行监听以便对其作出响应。观察者模式也被称为“订阅者模式”。
7.命令模式是对方法调用进行封装的方式,用命名模式可以对方法调用进行参数化和传递,然后在需要的时候再加以执行。
8.职责链模式用来消除请求的发送者和接收者之间的耦合。
JavaScript设计模式都有哪些?
单体(Singleton)模式: 绝对是JavaScript中最基本最有用的模式。
单体在JavaScript的有多种用途,它用来划分命名空间。可以减少网页中全局变量的数量(在网页中使用全局变量有风险);可以在多人开发时避免代码的冲突(使用合理的命名空间)等等。
在中小型项目或者功能中,单体可以用作命名空间把自己的代码组织在一个全局变量名下;在稍大或者复杂的功能中,单体可以用来把相关代码组织在一起以便日后好维护。
使用单体的方法就是用一个命名空间包含自己的所有代码的全局对象,示例:
- var functionGroup = {
- name:'Darren',
- method1:function(){
- //code
- },
- init:function(){
- //code
- }
- }
或者
- var functionGroup = new function myGroup(){
- this.name = 'Darren';
- this.getName = function(){
- return this.name
- }
- this.method1 = function(){}
- ...
- }
工厂(Factory)模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
工厂就是把成员对象的创建工作转交给一个外部对象,好处在于消除对象之间的耦合(何为耦合?就是相互影响)。通过使用工厂方法而不是new关键字及具体类,可以把所有实例化的代码都集中在一个位置,有助于创建模块化的代码,这才是工厂模式的目的和优势。
举个例子:你有一个大的功能要做,其中有一部分是要考虑扩展性的,那么这部分代码就可以考虑抽象出来,当做一个全新的对象做处理。好处就是将来扩展的时候容易维护——只需要操作这个对象内部方法和属性,达到了动态实现的目的。非常有名的一个示例——XHR工厂:
- var XMLHttpFactory = function(){}; //这是一个简单工厂模式
- XMLHttpFactory.createXMLHttp = function(){
- var XMLHttp = null;
- if (window.XMLHttpRequest){
- XMLHttp = new XMLHttpRequest()
- }else if (window.ActiveXObject){
- XMLHttp = new ActiveXObject("Microsoft.XMLHTTP")
- }
- return XMLHttp;
- }
- //XMLHttpFactory.createXMLHttp()这个方法根据当前环境的具体情况返回一个XHR对象。
- var AjaxHander = function(){
- var XMLHttp = XMLHttpFactory.createXMLHttp();
- ...
- }
工厂模式又区分简单工厂模式和抽象工厂模式,上面介绍的是简单工厂模式,这种模式用的更多也更简单易用。抽象工厂模式的使用方法就是 - 先设计一个抽象类,这个类不能被实例化,只能用来派生子类,最后通过对子类的扩展实现工厂方法。 示例:
- var XMLHttpFactory = function(){}; //这是一个抽象工厂模式
- XMLHttpFactory.prototype = {
- //如果真的要调用这个方法会抛出一个错误,它不能被实例化,只能用来派生子类
- createFactory:function(){
- throw new Error('This is an abstract class');
- }
- }
- //派生子类,文章开始处有基础介绍那有讲解继承的模式,不明白可以去参考原理
- var XHRHandler = function(){
- XMLHttpFactory.call(this);
- };
- XHRHandler.prototype = new XMLHttpFactory();
- XHRHandlerXHRHandler.prototype.constructor = XHRHandler;
- //重新定义createFactory 方法
- XHRHandler.prototype.createFactory = function(){
- var XMLHttp = null;
- if (window.XMLHttpRequest){
- XMLHttp = new XMLHttpRequest()
- }else if (window.ActiveXObject){
- XMLHttp = new ActiveXObject("Microsoft.XMLHTTP")
- }
- return XMLHttp;
- }
桥梁(bridge)模式:在实现API的时候,桥梁模式灰常有用。在所有模式中,这种模式最容易立即付诸实施。
桥梁模式可以用来弱化它与使用它的类和对象之间的耦合,就是将抽象与其实现隔离开来,以便二者独立变化;这种模式对于JavaScript中常见的时间驱动的编程有很大益处,桥梁模式最常见和实际的应用场合之一是时间监听器回调函数。先分析一个不好的示例:
- element.onclick = function(){
- new setLogFunc();
- };
为什么说这个示例不好,因为从这段代码中无法看出那个LogFunc方法要显示在什么地方,它有什么可配置的选项以及应该怎么去修改它。换一种说法就是,桥梁模式的要诀就是让接口“可桥梁”,实际上也就是可配置。把页面中一个个功能都想象成模块,接口可以使得模块之间的耦合降低。
掌握桥梁模式的正确使用收益的不只是你,还有那些负责维护你代码的人。把抽象于其实现隔离开,可独立地管理软件的各个部分,bug也因此更容易查找。
桥梁模式目的就是让API更加健壮,提高组件的模块化程度,促成更简洁的实现,并提高抽象的灵活性。一个好的示例:
- element.onclick = function(){ //API可控制性提高了,使得这个API更加健壮
- new someFunction(element,param,callback);
- }
注:桥梁模式还可以用于连接公开的API代码和私有的实现代码,还可以把多个类连接在一起。在文章封装介绍的部分提到过特权方法,也是桥梁模式的一种特例。《JS设计模式》上找的示例,加深大家对这个模式的理解:
- //错误的方式
- 2 //这个API根据事件监听器回调函数的工作机制,事件对象被作为参数传递给这个函数。本例中并没有使用这个参数,而只是从this对象获取ID。
- 3 addEvent(element,'click',getBeerById);
- 4 function(e){
- 5 var id = this.id;
- 6 asyncRequest('GET','beer.url?id=' + id,function(resp){
- 7 //Callback response
- 8 console.log('Requested Beer: ' + resp.responseText);
- 9 });
- 10 }
- 11
- 12 //好的方式
- 13 //从逻辑上分析,把id传给getBeerById函数式合情理的,且回应结果总是通过一个毁掉函数返回。这么理解,我们现在做的是针对接口而不是实现进行编程,用桥梁模式把抽象隔离开来。
- 14 function getBeerById(id,callback){
- 15 asyncRequest('GET','beer.url?id=' + id,function(resp){
- 16 callback(resp.responseText)
- 17 });
- 18 }
- 19 addEvent(element,'click',getBeerByIdBridge);
- 20 function getBeerByIdBridge(e){
- 21 getBeerById(this.id,function(beer){
- 22 console.log('Requested Beer: ' + beer);
- 23 });
- 24 }
装饰者(Decorator)模式:这个模式就是为对象增加功能(或方法)。
动态地给一个对象添加一些额外的职责。就扩展功能而言,它比生成子类方式更为灵活。
装饰者模式和组合模式有很多共同点,它们都与所包装的对象实现统一的接口并且会把任何方法条用传递给这些对象。可是组合模式用于把众多子对象组织为一个整体,而装饰者模式用于在不修改现有对象或从派生子类的前提下为其添加方法。
装饰者的运作过程是透明的,这就是说你可以用它包装其他对象,然后继续按之前使用那么对象的方法来使用,从下面的例子中就可以看出。还是从代码中理解吧:
- 1 //创建一个命名空间为myText.Decorations
- 2 var myText= {};
- 3 myText.Decorations={};
- 4 myText.Core=function(myString){
- 5 this.show = function(){return myString;}
- 6 }
- 7 //第一次装饰
- 8 myText.Decorations.addQuestuibMark =function(myString){
- 9 this.show = function(){return myString.show()+'?';};
- 10 }
- 11 //第二次装饰
- 12 myText.Decorations.makeItalic = function(myString){
- 13 this.show = function(){return '<li>'+myString.show()+'</li>'};
- 14 }
- 15 //得到myText.Core的实例
- 16 var theString = new myText.Core('this is a sample test String');
- 17 alert(theString.show()); //output 'this is a sample test String'
- 18 theString = new myText.Decorations.addQuestuibMark(theString);
- 19 alert(theString.show()); //output 'this is a sample test String?'
- 20 theString = new myText.Decorations.makeItalic (theString);
- 21 alert(theString.show()); //output '<li>this is a sample test String</li>'
从这个示例中可以看出,这一切都可以不用事先知道组件对象的接口,甚至可以动态的实现,在为现有对象增添特性这方面,装饰者模式有极大的灵活性。
如果需要为类增加特性或者方法,而从该类派生子类的解决办法并不实际的话,就应该使用装饰者模式。派生子类之所以会不实际最常见的原因是需要添加的特性或方法的数量要求使用大量子类。
组合(Composite)模式:将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
组合模式是一种专为创建Web上的动态用户界面而量身定制的模式。使用这种模式,可以用一条命令在多个对象上激发复杂的或递归的行为。组合模式擅长于对大批对象进行操作。
组合模式的好处:1.程序员可以用同样的方法处理对象的集合与其中的特定子对象;2.它可以用来把一批子对象组织成树形结构,并且使整棵树都可被便利。
组合模式适用范围:1.存在一批组织成某处层次体系的对象(具体结构可能在开发期间无法知道);2.希望对这批对象或其中的一部分对象实话一个操作。
其实组合模式就是将一系列相似或相近的对象组合在一个大的对象,由这个大对象提供一些常用的接口来对这些小对象进行操作,代码可重用,对外操作简单。例如:对form内的元素,不考虑页面设计的情况下,一般就剩下input了,对于这些input都有name和value的属性,因此可以将这些input元素作为form对象的成员组合起来,form对象提供对外的接口,便可以实现一些简单的操作,比如设置某个input的value,添加/删除某个input等等。
这种模式描述起来比较吃力,我从《JS设计模式》上找个一个实例,大家还是看代码吧:先创建组合对象类
- 1 // DynamicGallery Class
- 2 var DynamicGallery = function (id) { // 实现Composite,GalleryItem组合对象类
- 3 this.children = [];
- 4 this.element = document.createElement('div');
- 5 this.element.id = id;
- 6 this.element.className = 'dynamic-gallery';
- 7 }
- 8 DynamicGallery.prototype = {
- 9 // 实现Composite组合对象接口
- 10 add: function (child) {
- 11 this.children.push(child);
- 12 this.element.appendChild(child.getElement());
- 13 },
- 14 remove: function (child) {
- 15 for (var node, i = 0; node = this.getChild(i); i++) {
- 16 if (node == child) {
- 17 this.children.splice(i, 1);
- 18 break;
- 19 }
- 20 }
- 21 this.element.removeChild(child.getElement());
- 22 },
- 23 getChild: function (i) {
- 24 return this.children[i];
- 25 },
- 26 // 实现DynamicGallery组合对象接口
- 27 hide: function () {
- 28 for (var node, i = 0; node = this.getChild(i); i++) {
- 29 node.hide();
- 30 }
- 31 this.element.style.display = 'none';
- 32 },
- 33 show: function () {
- 34 this.element.style.display = 'block';
- 35 for (var node, i = 0; node = getChild(i); i++) {
- 36 node.show();
- 37 }
- 38 },
- 39 // 帮助方法
- 40 getElement: function () {
- 41 return this.element;
- 42 }
- 43 }
再创建叶对象类
- 1 var GalleryImage = function (src) { // 实现Composite和GalleryItem组合对象中所定义的方法
- 2 this.element = document.createElement('img');
- 3 this.element.className = 'gallery-image';
- 4 this.element.src = src;
- 5 }
- 6 GalleryImage.prototype = {
- 7 // 实现Composite接口
- 8 // 这些是叶结点,所以我们不用实现这些方法,我们只需要定义即可
- 9 add: function () { },
- 10 remove: function () { },
- 11 getChild: function () { },
- 12 // 实现GalleryItem接口
- 13 hide: function () {
- 14 this.element.style.display = 'none';
- 15 },
- 16 show: function () {
- 17 this.element.style.display = '';
- 18 },
- 19 // 帮助方法
- 20 getElement: function () {
- 21 return this.element;
- 22 }
- 23 }
现在我们可以使用这两个类来管理图片:
- 1 var topGallery = new DynamicGallery('top-gallery');
- 2 topGallery.add(new GalleryImage('/img/image-1.jpg'));
- 3 topGallery.add(new GalleryImage('/img/image-2.jpg'));
- 4 topGallery.add(new GalleryImage('/img/image-3.jpg'));
- 5 var vacationPhotos = new DyamicGallery('vacation-photos');
- 6 for(var i = 0, i < 30; i++){
- 7 vacationPhotos.add(new GalleryImage('/img/vac/image-' + i + '.jpg'));
- 8 }
- 9 topGallery.add(vacationPhotos);
- 10 topGallery.show();
- 11 vacationPhotos.hide();
门面(facade)模式:门面模式是几乎所有JavaScript库的核心原则
子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用,简单的说这是一种组织性的模式,它可以用来修改类和对象的接口,使其更便于使用。
门面模式的两个作用:1.简化类的接口;2.消除类与使用它的客户代码之间的耦合。
门面模式的使用目的就是图方面。
想象一下计算机桌面上的那些快捷方式图标,它们就是在扮演一个把用户引导至某个地方的接口的角色,每次操作都是间接的执行一些幕后的命令。
你在看这篇的博客的时候我就假设你已经有JavaScript的使用经验了,那么你一定写过或者看过这样的代码:
- 1 var addEvent = function(el,type,fn){
- 2 if(window.addEventListener){
- 3 el.addEventListener(type,fn);
- 4 }else if(window.attachEvent){
- 5 el.attachEvent('on'+type,fn);
- 6 }else{
- 7 el['on'+type] = fn;
- 8 }
- 9 }
这个就是一个JavaScript中常见的事件监听器函数,这个函数就是一个基本的门面,有了它,就有了为DOM节点添加事件监听器的简便方法。
现在要说门面模式的精华部分了,为什么说JavaScript库几乎都会用这种模式类。假如现在要设计一个库,那么最好把其中所有的工具元素放在一起,这样更好用,访问起来更简便。看代码:
- 1 //_model.util是一个命名空间
- 2 _myModel.util.Event = {
- 3 getEvent:function(e){
- 4 return e|| window.event;
- 5 },
- 6 getTarget:function(e){
- 7 return e.target||e.srcElement;
- 8 },
- 9 preventDefault:function(e){
- 10 if(e.preventDefault){
- 11 e.preventDefault();
- 12 }else{
- 13 e.returnValue = false;
- 14 }
- 15 }
- 16 };
- 17 //事件工具大概就是这么一个套路,然后结合addEvent函数使用
- 18 addEvent(document.getElementsByTagName('body')[0],'click',function(e){
- 19 alert(_myModel.util.Event.getTarget(e));
- 20 });
个人认为,在处理游览器差异问题时最好的解决办法就是把这些差异抽取的门面方法中,这样可以提供一个更一致的接口,addEvent函数就是一个例子。