Blog Logo

JavaScript设计模式

写于2015-10-28 11:48 阅读耗时17分钟 阅读量


今天开始讲解JavaScript设计模式,作为以前学Java的我在设计模式上或多或少接触过,可能自己理解程度有限,望大家多多见谅。 我准备讲20种设计模式,分别有:

  • 01.单例模式
  • 02.工厂模式
  • 03.桥接模式
  • 04.装饰者模式
  • 05.组合模式
  • 06.外观模式
  • 07.适配器模式
  • 08.代理模式
  • 09.观察者模式
  • 10.享元模式
  • 11.状态模式
  • 12.命令模式
  • 13.职责链模式
  • 14.构造函数模式
  • 15.建造者模式
  • 16.策略模式
  • 17.迭代器模式
  • 18.中介者模式
  • 19.模板方法模式
  • 20.原型模式

尽管讲解的模式很多,但是也不需要一一去记。因为学设计模式的目的并不是为了记住它的模式叫什么名,而是记住每个模式的代码为什么这样写,这样写的好处是什么。 当自己以后在看js插件源码或项目代码时,你能看出整个项目的架构,方便自己理解,那么JavaScript设计模式对你来说就非常有用。 我会把每个模式按照平时使用程度进行星级划分,星级低的不代表不重要,只是说在编写一般代码可能用的不多,但在编写架构代码却很常见。

1.单例模式(★★★★★)

var channelList = {
  page:'channelList',
  init:function(){
    //code
  },
  pageDidShow:function(){
    //code
  },
  pageDidHide:function(){
    //code
  },
  pageDestory:function(){
    //code
  }
}

总结:

特点及优势:单例模式就是保证一个类只有一个实例,其目的就是为了节约资源。 适用场合:用来划分命名空间。 划分命名空间的好处有以下两点: 1.可以减少网页中全局变量的数量(即window下面的变量) 2.可以在多人开发时避免代码的冲突


2.工厂模式(★★★★☆)

Ajax模块:

var XMLHttpFactory={};
XMLHttpFactory.createXMLHttp=function(){
    var XMLHttp=null;
    if(window.XMLHttpRequest){
        XMLHttp=new XMLHttpRequest()
    }else if(window.ActiveXObject){
        XMLHttp=new ActiveXObject("Microsoft.XMLHTTP")
    }
    return XMLHttp;
}

简单工厂模式:

var Page={};
Page.APage=function(){
    //code
}
Page.BPage=function(){
    //code
}
Page.CPage=function(){
    //code
}
...
Page.factory=function(type){
  return new Page[type];
}
var a=Page.factory('APage');
var b=Page.factory('BPage');

总结:

特点及优势:工厂模式无需使用new关键字指定具体类,而是在创建对象时才确定类。有助于创建模块化的代码,方便扩展。 适用场合:a.主要用在所实例化的类的类型不能在开发期间确定,而只能在运行期间才能确定的情况下。b.想创建一些包含成员对象的类但又不想把它们紧密耦合在一起。


3.桥接模式(★★★☆☆)

封装的东西:

function extend(parent,child){
  var child=child||{};
  for(var i in parent){
    if(typeof parent[i] === 'object'){
      child[i]=(parent[i].constructor === Array)?[]: {};
      extend(parent[i],child[i]);
    }else{
      child[i]=parent[i];
    }
  }
  return child;
}

桥接模式代码:

function SoftWare(){
    this.start=function(){}
};
var qq={},weibo={};
extend(SoftWare,qq);
extend(SoftWare,weibo);
qq.start=function(){
    console.log('使用QQ软件');
}
weibo.start=function(){
    console.log('使用微博软件');
}
function System(){
    this.run=function(software){}
};
var Android={},IOS={};
extend(System,Android);
extend(System,IOS);
Android.run=function(software){
    console.log('在安卓系统下');
    software.start();
}
IOS.run=function(software){
    console.log('在苹果系统下');
    software.start();
}
Android.run(qq);
IOS.run(weibo);

总结:

特点及优势:桥接模式是将抽象与其实现分离开来,以便二者独立变化。促进代码的模块化,促成更简洁的实现并提高抽象的灵活性。 桥接模式的参与者包括:抽象类、具体类、实现者、具体实现者。 适用场合:把一组类和函数连接起来。


4.装饰者模式(★★☆☆☆)

function MacBook(){
  this.hasSoftware=function(){
    console.log('have software');
  }
  this.cost=function(){
    return 8950;
  }
}
function AppleMouse(macbook){
  this.isMove=function(){
    console.log('is Move');
  }
  this.cost=function(){
    return macbook.cost()+450;
  }
}
function Pasting(macbook){
  this.cost=function(){
    return macbook.cost()+200;
  }
}

var myMacBook=new Pasting(new AppleMouse(new MacBook()));
myMacBook.cost();//9600

总结:

特点及优势:装饰者模式用于包装同接口的对象,通过重载方法的形式添加新功能。 适用场合:在为对象添加新特性时,使用了大量子类或不想改变使用该对象的代码的话,使用装饰者模式。


5.组合模式(★★★☆☆)

先创建树干:

var UL=function(id){
        this.children=[];
        this.element=document.createElement('ul');
        this.element.id=id;
    };
UL.prototype={
  add:function(child){
      this.children.push(child);
      this.element.appendChild(child.getElement());
  },
  remove:function(child){
      for(var node,i=0;node=this.getChild(i);i++){
           if(node == child){
                this.children.splice(i, 1);
                break;
           }
      }
      this.element.removeChild(child.getElement());
  },
  getChild:function(i){
      return this.children[i];
  },
  hide:function(){
      for(var node,i=0;node=this.getChild(i);i++) {
           node.hide();
      }
      this.element.style.display='none';
  },
  show:function(){
      this.element.style.display='block';
      for(var node,i=0;node=this.getChild(i);i++){
           node.show();
      }
  },
  getElement:function(){
      return this.element;
  }
}

再创建树叶:

var Li=function(text){
      this.element=document.createElement('li');
        var text=document.createTextNode(text);
        this.element.appendChild(text);
  };
Li.prototype = {
  add: function(){ },
  remove:function(){ },
  getChild:function(){ },
  hide:function(){
       this.element.style.display ='none';
  },
  show:function(){
       this.element.style.display ='block';
  },
  getElement:function(){
       return this.element;
  }
}

最后实现:

document.body.innerHTML="";
var li1=new Li('列表一');
var li2=new Li('列表二');
var ul=new UL('ww');
ul.add(li1);
ul.add(li2);
document.body.appendChild(ul.element);
ul.hide();

总结:

特点及优势:组合模式把一批子对象组织成树形结构,只需一条命令就可以操作树中的所有对象。 适用场合:特别适合于动态的HTML用户界面。


6.外观模式(★★★★★)

var addEvent=function(el,ev,fn){
    if(el.addEventListener){
        el.addEventListener(ev,fn,false);
    }else if(el.attachEvent){
        el.attachEvent('on'+ev,fn);
    }else{
        el['on'+ev]=fn;
    }
}; 

var Event={
    getEvent:function(e){
        return e||window.event;
    },
    getTarget:function(e){
        return e.target||e.srcElement;
    },
    stopPropagation:function(e){
        if(e.stopPropagation){
            e.stopPropagation();
        }else{
            e.cancelBubble=true;
        }
    },
    preventDefault:function(e){
        if(e.preventDefault){
            e.preventDefault();
        }else{
            e.retrunValue=false;
        }
    }
    stop: function (e) {
        this.preventDefault(e);//阻止默认行为
        this.stopPropagation(e);//阻止默认冒泡
    }
};

var $=function(selector){
    return document.querySelectorAll(selector);
}

总结:

特点及优势:外观模式是几乎所有JavaScript库(如JQuery、YUI、Prototype.js)的核心原则。外观模式可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。 适用场合:a.封装一些兼容浏览器的接口。b.简化重复性代码。


7.适配器模式(★★★★☆)

var param={name:'ww',age:2};
function people(name,age){}
function adaptePeople(param){
    people(param.name,param.age);
}
adaptePeople(param);

总结:

特点及优势:适配器模式是用一个新接口对现有的接口进行封装,好处在于无需对现有代码做大改动。 使用场合:现有接口,但其方法或属性不符合你的要求。


8.代理模式(★★★☆☆)

function FangDong(){
    this.room='1号房间';
}
FangDong.prototype.chuzu=function(){
    console.log('房东出租'+this.room);
};
function Proxy(){
    this.isInited=false;
    this.fangdong=null;
}
Proxy.prototype={
    init:function(callback){
        var self=this;
        if(this.fangdong==null){
            setTimeout(function(){
                self.fangdong=new FangDong();
                console.log('终于等到房东想租出去');
                callback();
                self.isInited=true;
            },5000);
        }
        this.interval=setInterval(function(){
            self.checkInit();
        },100);
    },
    checkInit:function(){
        if(this.isInited){
            clearInterval(this.interval);
        }
    },
    chuzu:function(){
        var self=this;
        this.init(function(){
            self.fangdong.chuzu();
            console.log('出租后收中介费');
        });
    }
};
var proxy = new Proxy();
proxy.chuzu();

总结:

特点及优势:代理模式控制对创建开销很大资源的对象的访问。 比如例子中讲到的中介如果没收到房东的委托就没有出租房,只有等有房东需要让中介帮忙,才能有房子,才能有钱赚,所有创建房东就需要时间。 适用场合:包装那些需要大量计算或较长时间才能实例化的类。


9.观察者模式(★★★★☆)

var PubSub={};
(function(p){
    var topics={},lastUid=-1;
    p.publish=function(topic,data){
        if(!topics.hasOwnProperty(topic)){
            return false;
        }
        var subscribers=topics[topic];
        for(var i=0,j=subscribers.length;i < j;i++){
          subscribers[i].func(topic,data);
        }
        return true;
    };
    p.subscribe=function(topic,func){
        if(!topics.hasOwnProperty(topic)){
            topics[topic]=[];
        }
        var token=(++lastUid).toString();
        topics[topic].push({token:token,func:func});
        return token;
    };
    p.unsubscribe=function(token){
        for(var m in topics){
            if(topics.hasOwnProperty(m)){
                for(var i=0,len=topics[m].length;i < len;i++){
                    if(topics[m][i].token === token){
                        topics[m].splice(i,1);
                        return token;
                    }
                }
            }
        }
    };
}(PubSub));

下面是用JQuery的on/off功能实现:

(function($){
    var o=$({});
    $.subscribe=function(){
        o.on.apply(o,arguments);
    };
    $.unsubscribe=function(){
        o.off.apply(o,arguments);
    };
    $.publish=function(){
        o.trigger.apply(o, arguments);
    };
} (jQuery));

总结:

特点及优势:DOM的事件监听器(addEventListener)就是一种内置的观察者。 观察者模式的执行过程:首先订阅特定的事件,然后等待事件的发生,当事件发生时,订阅方的回调函数会得到通知并执行。 因此观察者模式包括订阅、退订、发布方法。 适用场合:用于事件监听方面的优化。使用该模式可以削减事件注册监听的次数,让可观察对象借助一个事件监听器替你处理各种行为,从而降低内存消耗和提高互动性能。


10.享元模式(★★☆☆☆)

$('div').bind('click', function(){
    console.log($(this).attr('id'));//bad
    console.log(this.id);//good
});

总结:

特点及优势:享元模式可以大幅度减少需要实例化的类的数量,提升内存的性能。 如上面例子中的代码$(this)表示生成JQuery对象,意味着每次点击都会重新创建JQuery对象,这样就显得很不科学。 适用场合: 页面存在大量资源密集型对象,对浏览器的内存和CPU占用极大的网站。


11.状态模式(★★★☆☆)

var StateManager=function(){
  var states={
    ready: function( state ){
        console.log('开始下载');
    },
    downloading: function( state ){
        console.log('下载中');
    },
    downloadPasued: function( state ){
        console.log('下载暂停');
    },
    downloaded: function( state ){
        console.log('下载完毕');
    },
    downloadFail: function( state ){
        console.log('下载失败');
    }
  },
  changeState=function(state){
    states[state]&&states[state]();
  }
  return {
      changeState :changeState
  }
}
var stateManager=StateManager();
stateManager.changeState('ready');

总结:

特点及优势:状态模式可以把散落在世界各地的条件分支集中管理到一个类里,并且可以很容易的添加一种新的状态。 适用场合:a.一个操作中含有庞大的条件分支语句。 b.一个对象的行为取决于它的状态。


12.命令模式(★★★★☆)

var $={
    ajax:function(data){
        console.log(data);
        data.success("请求后响应的数据");
    }
};
var AjaxInterface={
    doData:function(operName,param,callback){
        $.ajax({
            type: "POST",
            url: "/user/" + operName,
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            cache: false,
            data:param,
            success: function(data) {
                callback.call(this, data);
            }
        });
    },
    add:function(param,callback){
        this.doData("add",param,function(data){
            console.log("添加的数据:"+data);
            callback.call(this,data);
        });
    },
    get:function(param,callback){
        this.doData("get",param,function(data){
            console.log("查询的数据:"+data);
            callback.call(this,data);
        });
    },
    update:function(param,callback){
        this.doData("update",param,function(data){
            console.log("修改的数据:"+data);
            callback.call(this,data);
        });
    }
};

var param={
    objName:"用户",
    id:1
};
AjaxInterface.get(param,function(data){
    console.log(data);
});

总结:

特点及优势:命令模式将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化。简单说命令模式就是对相似方法的封装。 适用场合:a.代码封装。 b.代码重构。


13.职责链模式(★★☆☆☆)

function Handler(successor){
    this.successor=successor || null;
}
Handler.prototype.handler=function(){
    if(this.successor){
        this.successor.handler();
    }
}
var p=new Handler({
    handler:function(){console.log('p');}
});
var div=new Handler(p);
var body=new Handler(div);
p.handler();div.handler();body.handler();//都是调用原型handler方法

var p2=new Handler({
    handler:function(){console.log('p2');}
});
var div2=new Handler(p2);
div2.handler=function(){
    Handler.prototype.handler.apply(this);
    console.log('div2');
}
var body2=new Handler(div2);
body2.handler=function(){
    Handler.prototype.handler.apply(this);
    console.log('body2');
}
body2.handler();//p2、div2、body2

总结:

特点及优势:JavaScript内部的事件冒泡和事件捕获用到了职责链模式。 适用场合:A对象请求B对象,B对象不处理请求C对象,C对象不处理清楚D对象。对事件处理程序过多的代码解决方式就使用职责链模式。


14.构造函数模式(★★★★★)

function People(name,age){
    if(!(this instanceof People)){
        return new People(name, age);
    }
    this.name=name;
    this.age=age;
}
People.prototype.say=function(){
    console.log('My name is '+this.name);
}
var p1=new People('ww',22);
p1.say();

var p2=People('zl',22);
p2.say();

总结:

特点及优势:构造函数用于创建特定类型的对象。 适用场合:创建新的类。


15.建造者模式(★★★☆☆)

$.ajax(function(){
    url:'',
    type:'',
    success:function(data){}
});
$('<div id="ww"><span></span></div>');

总结:

特点及优势:建造者模式可以将一个复杂对象的构建与其表示相分离。建造者模式让你只用知道结果,不用知道创建的过程。 如上面代码中ajax的回调success里面的data,你不需要知道data的创建过程,你只需要使用即可;$里面只需要传入要生成的HTML字符,而不需要关心HTML对象是如何生产的。 适用场合:代码中需要分步骤构建一个复杂的对象。


16.策略模式(★★★☆☆)

var Valid=function(res){
    var validataList={
        isEmptyNull:function(val){
            if(val){
                return false;
            }
            return true;
        },
        isNumber:function(val){
            return /^-?\d+$/.test(val);
        },
        isMaxLength:function(val,maxLen){
            return (val+"").length <= maxLen ? maxLen:0;
        },
        isPassword:function(val){
            return /^(\d|[a-z]|[A-Z]){6,18}$/.test(val);
        }
    };
    return{
        validata:function(data){
            var count=0,total=0;
            for(var i in res){
                total++;
                if(validataList[i](data,res[i]) == res[i]){
                    count++;
                };
            }
            if(total == count){
                return true;
            }
            return false;
        }
    };
};
var mobileValid=new Valid({
    isEmptyNull:false,
    isNumber:true,
    isMaxLength:11
});
var passwordValid=new Valid({
    isEmptyNull:false,
    isPassword:true,
});
var mobile='13688888888',password='ww123';
mobileValid.validata(mobile);
passwordValid.validata(password);

总结:

特点及优势:策略模式就是定义一系列的算法,把它们一个个封装起来,并且使它们可替换删减。 适用场合:a.封装算法。 b.封装几乎任何类型的规则。


17.迭代器模式(★★★★☆)

var $=(function(){
    var forEach=function(arr,callback){
        if(arr instanceof Array){
            for(var i=0,len=arr.length;i < len;i++){
                callback(i,arr[i]);
            }    
        }else{
            for(var i in arr){
                callback(i,arr[i]);
            }
        }
    }
    return{
        each:forEach
    };
}());
$.each([1,2,4,6],function(i,val){})
$.each({a:1,b:2,c:3},function(i,val){})

总结:

特点及优势:迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该方法中的内部表示。 适用场合:遍历集合。


18.中介者模式(★★★★★)

var mode1=Mode.create(),mode2=Mode.create();
var view1=View.create(),view2=View.create();
var controler1=Controler.create(mode1,view1,function(){})
var controler2=Controler.create(mode2,view2,function(){})

总结:

特点及优势:控制层便是位于表现层与模型层之间的中介者。 中介者模式的功能就是封装对象之间的交互,降低了系统对象之间的耦合性,使得对象易于独立的被复用。 适用场合:一组定义良好的对象,现在要进行复杂的通信。


19.模板方法模式(★★★★☆)

var Life=function(){
    this.DNA复制();
    this.出生();
    this.成长();
    this.衰老();
    this.死亡();
}
Life.prototype={
    DNA复制:function(){//自己不能做主},
    出生:function(){},
    成长:function(){},
    衰老:function(){},
    死亡:function(){}
}
var Mammal=function(){
    Life.apply(this,arguments);//继承生命
}
Life.prototype={
    DNA复制:function(){//自己不能做主},
    出生:function(){胎生();},
    成长:function(){},
    衰老:function(){},
    死亡:function(){}
}
var People=function(){
    Mammal.apply(this,arguments);//继承哺乳动物
}

总结:

特点及优势:模板方法模式预先定义一组算法,先把算法的不变部分抽象到父类,再将另外一些可变的步骤延迟到子类去实现。 模板方法是一种代码复用的基本技术,在类库中尤为重要,因为他们提取了类库中的公共行为。 适用场合:a.代码重构 b.代码架构


20.原型模式(★★★★☆)

function extend(child,parent){
    var Empty=function(){};
    Empty.prototype=parent.prototype;
    child.prototype=new Empty();
    child.prototype.constructor=child;
}

总结:

特点及优势:原型模式是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。 适用场合:a.实现继承 b.声明公用方法


经历一周多的总结与编写,20种设计模式终于讲解的差不多了。能看完到最后的人都是好样的,希望对你们能有所帮助。

Headshot of Maxi Ferreira

怀着敬畏之心,做好每一件事。