单例模式有两个要点,保证一个类只有一个实例,并提供访问该实例的全局访问点。
这篇文章通过一个简单的创建Mask的需求 , 一步步优化代码中 , 循序渐进 , 通俗易懂地讲解了单例的产生以及运用 , 加上了一些个人理解 .
假定需求 : 在点击按钮需要弹出一个遮罩层的时 . (例如 web.qq.com点击登录的时候)
创建 div :
var createMask = function(){
return document.body.appendChild(document.createElement(div));
}
给按钮添加点击回调方法 :
$('button').click(function(){
var mask = createMask();
mask.show();
})
现在的处理虽然可以解决需求, 但是每次点击按钮的时候都创建出一个新的div, 这很显然不是很好的解决办法
创建一个mask变量, 只创建一次div, 每次需要显示的时候, 只需要调用show();方法即可
var mask = document.body.appendChild(document.createElement('div'));
$('button').click(function(){
mask.show();
})
这样处理虽然表面上可以减少创建 和 移除 div 的次数, 可是如果我们从始至终都不需要使用这个div 遮罩呢? 那不是白白浪费了这个div, 对dom节点的任何操作都应该非常吝啬, 显然我们需要再次改进.
我们借助一个变量来判断是否创建过这个div
var mask;
var createMask = function(){
if (mask)
{
return mask;
}
else
{
mask = document,body.appendChild(document.createElement(div));
return mask;
}
}
这样处理看起来就好很多了, 完成了一个基本的单例, 既不会有多余的创建, 也不会白白创建一个不需要的div.
但是, 如果仔细研究这个函数, 还是能发现其中的问题, 函数体内改变了外界变量mask的引用, 在多人协作的项目中, createMask是个不安全的函数. 另一方面, mask这个全局变量并不是非需不可.
var createMask = function(){
var mask;
return function()
{
return mask || (mask = document.body.appendChild(document.createElement('div')))
}
}()
用了一个闭包把变量mask包起来, 使得这个函数是一个封闭的函数.
原文中提到, 在js中函数是第一型, 意味着函数也可以当参数传递.
它只能用于创建遮罩层. 假如我又需要写一个函数, 用来创建一个唯一的xhr对象呢? 能不能找到一个通用的singleton包装器.
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的函数式特性还不足以完全的消除声明和语句.