一 单例模式
单例模式的定义是产生一个类的唯一实例,但js本身是一种“无类”语言。很多讲js设计模式的文章把{}当成一个单例来使用也勉强说得通。因为js生成对象的方式有很多种,我们来看下另一种更有意义的单例。
有这样一个常见的需求,点击某个按钮的时候需要在页面弹出一个遮罩层。比如web.qq.com点击登录的时候.
JavaScript
var createMask = function(){ return document,body.appendChild( document.createElement(div) ); }
JavaScript
$( 'button' ).click( function(){ Var mask = createMask(); mask.show(); })
问题是, 这个遮罩层是全局唯一的, 那么每次调用createMask都会创建一个新的div, 虽然可以在隐藏遮罩层的把它remove掉. 但显然这样做不合理.
再看下第二种方案, 在页面的一开始就创建好这个div. 然后用一个变量引用它.
JavaScript
var mask = document.body.appendChild( document.createElement( ''div' ) ); $( ''button' ).click( function(){ mask.show(); } )
这样确实在页面只会创建一个遮罩层div, 但是另外一个问题随之而来, 也许我们永远都不需要这个遮罩层, 那又浪费掉一个div, 对dom节点的任何操作都应该非常吝啬.
如果可以借助一个变量. 来判断是否已经创建过div呢?
JavaScript
var mask; var createMask = function(){ if ( mask ) return mask; else{ mask = document,body.appendChild( document.createElement(div) ); return mask; } }
看起来不错, 到这里的确完成了一个产生单列对象的函数. 我们再仔细看这段代码有什么不妥.
首先这个函数是存在一定副作用的, 函数体内改变了外界变量mask的引用, 在多人协作的项目中, createMask是个不安全的函数. 另一方面, mask这个全局变量并不是非需不可. 再来改进一下.
JavaScript
var createMask = function(){ var mask; return function(){ return mask || ( mask = document.body.appendChild( document.createElement('div') ) ) } }();
用了个简单的闭包把变量mask包起来, 至少对于createMask函数来讲, 它是封闭的.
前面那个单例还是有缺点. 它只能用于创建遮罩层. 假如我又需要写一个函数, 用来创建一个唯一的xhr对象呢? 能不能找到一个通用的singleton包装器.
js中函数是第一型, 意味着函数也可以当参数传递. 看看最终的代码.
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') ); });
用一个变量来保存第一次的返回值, 如果它已经被赋值过, 那么在以后的调用中优先返回该变量. 而真正创建遮罩层的代码是通过回调函数的方式传人到singleton包装器中的. 这种方式其实叫桥接模式. 关于桥接模式, 放在后面一点点来说.
然而singleton函数也不是完美的, 它始终还是需要一个变量result来寄存div的引用. 遗憾的是js的函数式特性还不足以完全的消除声明和语句.
二 简单工厂模式
简单工厂模式是由一个方法来决定到底要创建哪个类的实例, 而这些实例经常都拥有相同的接口. 这种模式主要用在所实例化的类型在编译期并不能确定, 而是在执行期决定的情况。 说的通俗点,就像公司茶水间的饮料机,要咖啡还是牛奶取决于你按哪个按钮。
<meta charset="utf-8"/> <script type="text/javascript"> //工厂该有厂长来决定到底运行哪条产品线 //消费者=》子类 var gongchang={}; gongchang.chanyifu=function(){ this.gongren=50; alert('我们有'+this.gongren+'个工人'); }; gongchang.chanxie=function(){ alert('产鞋子~'); }; gongchang.yunshu=function(){ alert('运输~'); }; gongchang.changzhang=function(para){ //new js 使用了构造函数模式 单例模式 new gongchang[para](); }; var me=gongchang.changzhang('chanyifu'); </script>
模式作用:
1.对象的构建十分复杂;
2.需要依赖具体的环境创建不同的实例;
3.处理大量具有相同属性的小对象;
注意事项:
1.不能滥用工厂,有时候仅仅是给代码增加复杂度;
三 观察者模式
观察者模式( 又叫发布者-订阅者模式 )应该是最常用的模式之一. 在很多语言里都得到大量应用. 包括我们平时接触的dom事件. 也是js和dom之间实现的一种观察者模式.
先看看生活中的观察者模式,好莱坞有句名言. “不要给我打电话, 我会给你打电话”. 这句话就解释了一个观察者模式的来龙去脉。 其中“我”是发布者, “你”是订阅者。
再举个例子,我来公司面试的时候,完事之后每个面试官都会对我说:“请留下你的联系方式, 有消息我们会通知你”。 在这里“我”是订阅者, 面试官是发布者。所以我不用每天或者每小时都去询问面试结果, 通讯的主动权掌握在了面试官手上,而我只需要提供一个联系方式。观察者模式可以很好的实现2个模块之间的解耦。