STAY HUNGRY , STAY FOOLISH.

求知若饥,虚心若愚。

       浏览:

JavaScript设计模式

今天开始讲解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.单例模式(★★★★★)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var channelList = {
  page:'channelList',
  init:function(){
    //code
  },
  pageDidShow:function(){
    //code
  },
  pageDidHide:function(){
    //code
  },
  pageDestory:function(){
    //code
  }
}

总结:

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


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

Ajax模块:

1
2
3
4
5
6
7
8
9
10
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;
}

简单工厂模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.桥接模式(★★★☆☆)

封装的东西:

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

桥接模式代码:

1
2
3
4
5
6
7
8
9
10
11
12
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('使用微博软件');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
1
2
Android.run(qq);
IOS.run(weibo);

总结:

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.组合模式(★★★☆☆)

先创建树干:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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;
  }
}

再创建树叶:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
  }
}

最后实现:

1
2
3
4
5
6
7
8
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.外观模式(★★★★★)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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.适配器模式(★★★★☆)

1
2
3
4
5
6
var param={name:'ww',age:2};
function people(name,age){}
function adaptePeople(param){
people(param.name,param.age);
}
adaptePeople(param);

总结:

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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.观察者模式(★★★★☆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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功能实现:

1
2
3
4
5
6
7
8
9
10
11
12
(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.享元模式(★★☆☆☆)

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

总结:

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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.命令模式(★★★★☆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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.职责链模式(★★☆☆☆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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.构造函数模式(★★★★★)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.建造者模式(★★★☆☆)

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

总结:

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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.迭代器模式(★★★★☆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.中介者模式(★★★★★)

1
2
3
4
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.模板方法模式(★★★★☆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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.原型模式(★★★★☆)

1
2
3
4
5
6
function extend(child,parent){
var Empty=function(){};
Empty.prototype=parent.prototype;
child.prototype=new Empty();
child.prototype.constructor=child;
}

总结:

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


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