JavaScript设计模式

本文主要参考了AlloyTeam的Blog
图说设计模式详细汇总了各种设计模式

设计模式主要是为了弥补语言的缺陷,弥补不同语言弱点的一种通用的解决方案

对于不同的语言,设计模式有不同的含义,有些设计模式在JavaScript中就可能并没有作用(比如装饰者模式)

单例模式

含义

保证一个类只有一个实例,并提供一个访问它的全局访问点

实现

创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用。

对于JavaScript来说,勉强可以认为一个对象{}就是一个单例,但是下面探讨一个更有意义的例子

应用

Modal界面背后需要一个DIV来形成阴影遮罩,这个遮罩层应该永远是一个DIV而不是重复创建。并且只在需要的时候创建

1
2
3
4
5
6
var createMask = function(){
var mask;
return function(){
return mask || ( mask = document.body.appendChild( document.createElement('div') ) )
}
}()

这就是一个实例,通过一个闭包,利用私有变量mask追踪是否创建过实例。createMask第一次使用会创建该实例,之后只返回这个实例

可以将这个闭包抽象出来,接收一个函数形成一个 Singleton 的包装器

1
2
3
4
5
6
7
8
9
10
var singleton = function( fn ){
var result;
return function(){
return result || ( result = fn .apply( this, arguments ) );
}
}
var createMask = singleton( function(){
return document.body.appendChild( document.createElement('div') );
})

简单工厂模式

含义

简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

比如要获取圆形、方形、菱形三个按钮,不使用工厂模式,我们需要声明三个不同类,然后分别实例化,通过工厂方法可以直接用Button工厂传递不同参数

实现

简单来说,就是通过判断函数参数,根据参数不同返回不同的实例

1
2
3
4
5
if (arg === 'a') {
return new A();
} else if (arg === 'b') {
return new B();
}

应用

使用同一个函数来生成不同的XHR实例,包括post,get和jsonp

1
2
var request1 = Request('cgi.xx.com/xxx' , 'get');
var request2 = Request('cgi.xx.com/xxx' , 'jsonp' );

之前提到过的Button也是一个常见例子

事实上,JavaScript的new操作符也可以看作一个工厂方法,根据new后面的参数生成不同的实例

1
2
3
4
5
6
7
new A(arg);
//等价于
function new (fn, arg) {
this = {};
fn.apply(this, arg);
return this;
}

观察者模式

定义

定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新

观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

实现

发布者维护一个数组,订阅者订阅后被记录在数组中,当事件发生,发布者遍历数组通知订阅者

1
2
3
4
5
6
7
8
//注册函数,注册事件和响应的回调函数
var obj = {};
listen = function( key, eventfn ) { //eventfn是回调函数, key就是触发事件.
var stack, _ref; //stack是盒子
stack = ( _ref = obj[key] ) != null ? _ref : obj[ key ] = []; //如果该事件没人注册,建立该栈,每个事件一个栈
return stack.push( eventfn ); //回调函数入该事件的栈
};
//当key事件发生,遍历obj[key], 调用

应用

最典型的应用是JavaScript中的事件处理机制。通过该模型我们就可以自己设定事件了

在MVVM框架中,许多Model->View的绑定也是通过PubSub模式实现的,关于双向数据绑定可以参看这篇Blog

适配器模式

定义

将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)

实现

根据不同的情况,将现有的接口(参数)转换成API可以识别的格式

应用

例如有接口max(a, b, c)接受三个参数来计算三个参数a,b,c的,
我们现在只有一个数组,希望得到Max(array)这样的接口

1
2
3
4
5
6
7
var arrayMax = Adapter(max);
function Adapter (max) {
return function(array) {
return max(array[0], array[1], array[2]);
}
}

代理模式

定义

代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用

实现

声明一个代理函数,在代理层做一些处理来操作底层API。

主要目的是抽象出更一般的API在底层使用,而把特定的操作和处理在代理层完成。或者提升操作性能

与适配器模式最大的不同在于适配器模式只是转换接口实现对旧API的兼容,而代理模式进行了需要的处理

应用

React等Virtual DOM算法中,用一个JavaScript Object来代理真实的DOM,形成缓存层来提高操作性能

各种库对AJAX的操作也是一种代理模式,代理了对XMLHttpRequest的操作