[Javascript]单例模式(singleton )

Javascript中大家都很习惯用new运算符创建实例。现在看看另外一种创建实例的方法------单例模式。

单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

 单例模式的特点有:

1,该类只有一个实例
2,该类自行创建该实例(在该类内部创建自身的实例对象)
3,向整个系统公开这个实例接口 。

 下面我们分析一下实现的可行性

1个特点:Javascript是不支持重载的,如果不能被new的话,多次调用会被覆盖所以第一点也很好实现

2个特点:自行创建实例这点我们用匿名包装器也比较好实现

2个特点:javascript是运行在宿主环境中的,我们向global对象公开就可以了,浏览器中window相当于global

那么从上几点分析,是有可行性的。我们先实现一个雏形。我用一个拖拽列表控件来使用这个单例模式。

这是一个全局变量,我们可以用多种方式来做,如:

普通的:var sortList=(function(){})(); 
文艺的:window.sortList=(function(){})(); this.sortList=(function(){})()
xxx的:sortList=(function(){})(); 

文艺的第一种可以让大家一眼就看到他是一个全局变量。选取那种要看个人爱好和团队。

 

接下来设计我们的代码:

 Htmlcss部分

1.俗话说html是基石,我们先弄好骨架。

<ol id="list">
<li>1 </li>
<li>2 </li>
<li>3 </li>
<li>4 </li>
<li>5 </li>
<li>6 </li>
</ol>

  

2.为了让html跟易用,给他穿上拉风的衣服。

<style type="text/css">
ol{ list-style: url(none) none; }
li{ width: 200px; height: 30px; margin-bottom: 5px; color: #000; text-indent: 25px; font: bold 14px/30px Arial, Helvetica, sans-serif; background: #EEE; border: 1px solid #ccc; border-ridus:5px; border-radius:25px; box-shadow: 4px 5px #fff inset; }
.tips { border:dashed 2px red;background:#EEE; }
#prara p { border-radius:3px; background:#EEE; padding: 10px; }
</style>

万事具备现在来用脚本控制行为。

 

Javascript部分

 

1.使用匿名包装器。

var sortList=(function(window,doc,undefined){})(window,document); 

这样处理一下,可以把全局变量都变成我们这个单例的局部变量,以避免运行时访问全局变量对象以提高效率。

接下来我们声明各种要使用到的变量。

var sortList=(function(window,doc,undefined){
var lis=null,replace=null,origin=[0,0],parent=null,tag=null,tips=null,listWidth=0,toolkit={};
})(window,document); 

Toolkit是我常用的一个微型类库,有常用的事件处理方法和坐标计算模拟用户事件等等。

写程序时我们要考虑到程序的易用性,就是给使用的人不用考虑内部是怎样实现的,只要会调用就可以了。同时还要考虑到灵活性不能过分依赖结构,只要用一些简单的钩子就可以起到csshtmldom三者关联起来。符合这样条件的有idclass,简直太方便了。Idclass既是html属性有可以当css的选择器有可以用domapi来获取(getElementById/getElementsByClassName/selector API)。可拖动的标签也要通过参数可配置这样才够灵活。

我们在单体里面返回一个匿名函数,单体执行以后我们通过配置返回函数的参数来生成实例。

 

var sortList=(function(window,doc,undefined){
var lis=null,replace=null,origin=[0,0],parent=null,tag=null,tips=null,listWidth=0,toolkit={};
 return function(O){
};
})(window,document); 

 

2.设计方案

 这个拖拽排序列表可能有多种实现方案,那么怎么自己去设计呢?

首先我们要使列表项成为可拖拽元素,因为他只要实现上下排序,所以我们只要处理他的top值就可以了,要实现这点要使这个元素绝对定位。这样有产生了一个问题,如果我们使一个列表项变为可绝对定位,那么他将不再占位这样会造成页面晃动。怎么处理这个问题呢?

我们在拖拽元素时克隆一下当前的元素当做占位,插入到当前元素的位置,并且在改元素移动时判断和其他列表项的位置关系,把他插入到相应的位置。释放鼠标时,用拖拽元素替换拖拽元素并移除拖拽元素。这样就解决了占位问题,用能够用做提示,一举两得这是不错。

有了理论做支持我们试着用代码去实现他。

3.如果在每个元素上绑定事件的话这样既浪费资源有不能解决动态添加的元素,事件委托是一个好同志。我们监听docment上的mousedown事件如果事件源在我们想要处理的范围内那么就派发事件处理程序。记录鼠标和元素的位置关系,以及该target和其他元素的位置关系。

 

var sortList=(function(window,doc,undefined){
var lis=null,replace=null,..................,toolkit={.......};
function mousedownSortableList(e){};
function creatReplaceElement(O,E,P){};
function mousemoveCheckThreshold(e){};
return function(O){
};
})(window,document);

 

4.当鼠标按下并移动时,我们设置目标元素targettop值移动拖拽到相应位置,并且克隆一个target生成一个占位元素。判断占位元素的位置把他插入到相应位置。

5.当鼠标释放时,用target去替换占位元素,并且移除占位元素。

 

var sortList=(function(window,doc,undefined){
  var lis=null,replace=null,..................,toolkit={.......};
  function mousedownSortableList(e){};
  function creatReplaceElement(O,E,P){};
  function mousemoveCheckThreshold(e){};
  function mouseupCancelThreshold(e){};
  return function(O){

  };
})(window,document);

 

如果作为一个架构师,工作已经完成了。下面的部分可以交给小弟完成了。但是一般前这样的小控件是不好意思申请小弟的。下面的工作就是填入代码让程序跑起来,具体工作就不一步步演示了。直接上源码。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>sortlist</title>
<style type="text/css">
ol
{ list-style: url(none) none; }
li
{ width: 200px; height: 30px; margin-bottom: 5px; color: #000; text-indent: 25px; font: bold 14px/30px Arial, Helvetica, sans-serif; background: #EEE; border: 1px solid #ccc; border-ridus:5px; border-radius:25px; box-shadow: 4px 5px #fff inset; }
.tips
{ border:dashed 2px red;background:#EEE; }
#prara p
{ border-radius:3px; background:#EEE; padding: 10px; }
</style>
</head>

<body>
<ol id="list">
<li>
1
</li>
<li>
2
</li>
<li>
3
</li>
<li>
4
</li>
<li>
5
</li>
<li>
6
</li>
</ol>

<div id="prara">
<p>
1
</p>
<p>
2
</p>
<p>
3
</p>
<p>
4
</p>
<p>
5
</p>
<p>
6
</p>
</div>
<script type="text/javascript">

var sortList = function(window,doc,undefined){
var lis=null,replace=null,origin=[0,0],parent=null,tag=null,tips=null,listWidth=0,
toolkit
={getEvent:function(b){return b||window.event},getTarget:function(b){return b.srcElement||b.target},stopEvent:function(b){b=toolkit.getEvent(b);(b.returnValue||b.preventDefault)&&(b.returnValue=false||b.preventDefault());(b.cancelBubble||b.stopPropagation)&&(b.cancelBubble=false||b.stopPropagation())},getClinetRect:function(b){var g=b.getBoundingClientRect(),c=(c={left:g.left,right:g.right,top:g.top,bottom:g.bottom,height:(g.height?g.height:(g.bottom-g.top)),width:(g.width?g.width:(g.right-g.left))});return c},getScrollPosition:function(){var b=[0,0];window.pageYOffset?(b=[window.pageXOffset,window.pageYOffset]):(b=[document.documentElement.scrollLeft,document.documentElement.scrollTop]);return b},addEvent:function(f,e,d,c){var b=arguments.callee;f.attachEvent&&(b=function(i,h,g){i.attachEvent("on"+h,g)}).apply(this,arguments);f.addEventListener&&(b=function(i,h,g){i.addEventListener(h,g,c||false)}).apply(this,arguments);f["on"+e]&&(b=function(i,h,g){i["on"+h]=function(){g()}}).apply(this,arguments)},removeEvent:function(f,e,d,c){var b=arguments.callee;f.detachEvent&&(b=function(i,h,g){i.detachEvent("on"+h,g)}).apply(this,arguments);f.removeEventListener&&(b=function(i,h,g){i.removeEventListener(h,g,c||false)}).apply(this,arguments);f["on"+e]&&(b=function(i,h,g){i["on"+h]=null}).apply(this,arguments)}};


function mousedownSortableList(e){
e
=toolkit.getEvent(e);
var target=toolkit.getTarget(e),pos=null;

while(target.nodeName.toLowerCase()!==tag){
target
=target.parentNode;
};
doc.currentTarget
=target,pos=toolkit.getClinetRect(target),origin=[e.clientX-pos.left,e.clientY-pos.top],
listWidth
=target.offsetWidth;
toolkit.addEvent(doc,
'mousemove', mousemoveCheckThreshold, false);
toolkit.addEvent(doc,
'mouseup', mouseupCancelThreshold, false);
toolkit.stopEvent(e);
};
function creatReplaceElement(O,E,P){
replace
|| (
replace
=O.cloneNode(true),
replace.style.cssText
='height:'+(O.style.height-4)+'px;',
replace.className
=tips,
replace.innerHTML
='放在这里?'
);
(P
===-1) && E.parentNode.insertBefore(replace,E);
(P
===1) && E.parentNode.insertBefore(replace,E.nextSibling);
};
function mousemoveCheckThreshold(e){
e
=toolkit.getEvent(e);
var target=doc.currentTarget,i=lis.length,pos=null,scroll=toolkit.getScrollPosition();

try{target.style.cssText='top:'+(e.clientY-origin[1]+scroll[1])+'px;position:absolute;z-index:100;opacity:.9;filter:alpha(opacity="90");width:'+listWidth+'px;overflow:hidden;';}catch(e){};
for(;i>0;){
lis[
--i]!==target && (
pos
=toolkit.getClinetRect(lis[i]),
((e.clientY
>=pos.top) && (e.clientY < pos.bottom))&&(
creatReplaceElement(target,lis[i],e.clientY
<=(pos.top+lis[i].offsetHeight/2)?-1:1)
)
)
};

};
function mouseupCancelThreshold(e){
try{doc.currentTarget.style.cssText='';replace.parentNode.replaceChild(doc.currentTarget,replace)}catch(e){};
toolkit.removeEvent(doc,
'mousemove',mousemoveCheckThreshold,false);
toolkit.removeEvent(doc,
'mouseup',mouseupCancelThreshold,false);
doc.currentTarget
=null,replace=null;
toolkit.stopEvent(e);
};
return function(O){
parent
=O.parent,tag=O.tag || 'li',tips=O.tips || 'sortListTips';
if(!doc.getElementById(parent)) return false;
lis
=doc.getElementById(parent).getElementsByTagName(tag);
var i=lis.length
for(;i>0;toolkit.addEvent(lis[--i],'mousedown',mousedownSortableList,false),lis[i].style.cursor='move'){};
};
}(window,document);

//sortList({parent:"list",tag:"li",tips:'tips'});
sortList({parent:"prara",tag:"p"});
//拖拽排序列表单例 最好不要同时启用两个。
</script>
</body>
</html>


注意这是一个单例实现的控件,所以一个页面上只能有一个实例,有两个当然也没大的问题。Javascript没有重载导致覆盖。脚本内部也做了相应的容错,搞观看不同的实例,只能注释掉一个(当然移动调用顺序也可以)。大家都习惯了javascript的多实例,用构造函数创建一个类,用new运算符生成多个实例。这样使用一个单例的情况并不多,比如一个页面上今天可能用了一个选项卡,明天有可能有用两个。单例的局限性还是比较大的。要使用单例模式一定要明确单个window实例上是不是只使用一次某个功能,这样才适合使用单例。

 

这个例子是为了单例而单例的,当然也很容易改造成类式,有兴趣可以自己试一下。但是单例也有自己的优势,如果从其他语言转过来的同学,非常熟悉而且易上手,对原型继承不是很熟悉的可以尝试一下,关于单例的使用场景欢迎大家入群讨论职友集(7553571)。

posted @ 2011-12-15 16:38  小玉西瓜  阅读(1978)  评论(6编辑  收藏  举报