使用JavaScript闭包,以工厂模式实现定时器对象

原始对象写法

  一般工作中写Javascript代码,主要写全局函数,并组织函数之间的调用,确实比较低级,

于是想利用面向对象的思想应用到JS编码中。

  在火狐浏览器开发者网站上,看到一个实例利用对象技术实现的定时器例子,网址如下,

https://developer.mozilla.org/en-US/docs/Web/API/window.clearTimeout#Example

也贴出代码,如下,其实现是利用对象技术,在对象定义了三个函数(开始、取消 和 时间到的触发函数),

定时器句柄 timeoutID 作为定向的属性存储。

优点,自然比直接使用setTimeout函数更加集成,对功能做了封装,方便维护和管理。

缺点,只能支持一个计时器实例,且封装的过程,没有做到信息隐藏(对象中的 句柄 和 三个函数都可以被调用)。

<html>
<head>
<style> </style>
</head>
<body> <script type='text/javascript'>
var alarm = {
remind: function(aMessage) { alert(aMessage); delete this.timeoutID;
}, setup: function() {
this.cancel();
var self = this;
this.timeoutID = window.setTimeout(function(msg) {self.remind(msg);}, 1000, "Wake up!");
}, cancel: function() {
if(typeof this.timeoutID == "number") {
window.clearTimeout(this.timeoutID);
delete this.timeoutID;
}
}
};
window.onclick = function() { alarm.setup() };
</script>
</body>
</html>

闭包方法

下面应用工厂模式和闭包技术,解决上例中的缺点, 支持多实例计时器应用 并 明确开放定时器开放的接口。

如下代码中,

1、createTimer 为工厂函数 , 入参为计时结束触发的函数, 和options可选参数(目前只有timeout,默认为1秒);

2、每次调用createTimer生成一个定时器对象,即createTimer中 return出来的对象,

此对象定义的属性和函数,即为定时器开放接口 , 包括 start 和 stop。

3、定时器句柄 timerHandle 作为内部变量,不直接开放, 通过getTimerID函数访问,同时start赋值,被stop清空。

4、函数 fnAlarm ,也是作为内部变量,不开放,此为计时结束触发函数,createTimer第一参数(fnAlarmIn)的重载赋值,

此函数调用后会清空定时器句柄。

  开放接口 start stop 和 getTimerID, 被return到createTimer之外,

同时它们对createTimer内部变量的timerHandle 和 fnAlarm的依赖,构成了闭包。

闭包我理解是构造出来的一个封闭的运行环境, 即被return出去的这些函数,依赖的运行环境仍然跟这些函数绑定,没有切换到全局环境中去,这些函数可以继续对被return之前的环境变量进行读写。

-- 概念中心是构造一个运行环境,此环境包括 全局变量 和 函数入参 和 函数局部变量。

闭包的定义: 参考 下面文档, 其中牵扯到定义环境和引用环境, 当不一样,才构成这个:

http://www.ibm.com/developerworks/cn/linux/l-cn-closure/index.html

  显然闭包的技术天然含义满足设计模式中的工厂模式。

<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
</head>
<body>
<label>timer msg :</label> </br>
<textarea id="timer_msg" style="width:500px; height:300px"></textarea>
<script>
$.fn.extend({printMsg : function(msg){
var oldmsg = $(this).val();
var newmsg = oldmsg + "\n" + msg;
$(this).val(newmsg);
}}); function createTimer(fnAlarmIn, options)
{
/* external settable */
var timeout = 1000;
var fnAlarm = function(){
$("#timer_msg").printMsg(timerHandle.toString()
+ "-please add custom fnAlarm")
}; /* inner maintain variable */
var timerHandle; /* set external variable */
if ( fnAlarmIn )
{
fnAlarm = fnAlarmIn;
} if ( options && options.timeout )
{
timeout = options.timeout;
} return {
start: function(){
timerHandle = setTimeout(function(){
fnAlarm();
delete this.timerHandle;
},timeout);
$("#timer_msg").printMsg(timerHandle.toString() + "-timer started");
},
stop: function(){
clearTimeout(timerHandle)
delete this.timerHandle;
$("#timer_msg").printMsg(timerHandle.toString() + "-timer stoped");
},
getTimerID: function(){
return timerHandle;
}
};
} var timer1 = createTimer();
timer1.start();
timer1.stop(); var timer2 = createTimer(function(){
$("#timer_msg").printMsg("timer custom alarm");
}, {timeout:2000});
timer2.start(); var timer3 = createTimer(function(){
$("#timer_msg").printMsg(timer3.getTimerID()+"-timer custom alarm");
}, {timeout:3000});
timer3.start();
</script>
</body>
</html>  

改进

  上例中,定时器内部的函数中的打印都带有定时器句柄前缀,目的是对于多实例情况下跟踪各个实例的运行状态;

定时器内部是这样,但是定时器触发函数中的打印是没有句柄的,如timer2中触发函数的打印;

如果需要句柄来区分,就像timer3中,利用getTimerID接口来获取,然后打印。

  此获取句柄方法可行,但是每次都要使用 timer3.getTimerID() 来调用,显然不满足面向对象的思想,

因为此函数是在timer3创建之后的响应函数,理应使用 this.getTimerID() 来获取。

  下面就来解决这个问题,首先参考获取网站上关于 setTimeout 计时结束触发函数中 this的解决办法,见下网址

https://developer.mozilla.org/zh-CN/docs/DOM/window.setTimeout

  其基本思路为, 重载setTimeout,在新的函数中, 保存当前调用setTimeout的对象,

然后在真实的setTimeout的计时结束触发函数中,使用this来引用此对象,此时就构成了闭包。

  引入setTimeout重载代码,并将setTimeout.call到定时器对象上, 并通过fnAlarm.call,将对象传递到用户自定义的函数中。

<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
</head>
<body>
<label>timer msg :</label> </br>
<textarea id="timer_msg" style="width:500px; height:300px"></textarea>
<script>
$.fn.extend({printMsg : function(msg){
var oldmsg = $(this).val();
var newmsg = oldmsg + "\n" + msg;
$(this).val(newmsg);
}}); var __nativeST__ = window.setTimeout;
window.setTimeout = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
var oThis = this, aArgs = Array.prototype.slice.call(arguments, 2);
return __nativeST__(vCallback instanceof Function ? function () {
vCallback.apply(oThis, aArgs);
} : vCallback, nDelay);
}; function createTimer(fnAlarmIn, options)
{
/* external settable */
var timeout = 1000;
var fnAlarm = function(){
$("#timer_msg").printMsg(timerHandle.toString()
+ "-please add custom fnAlarm")
}; /* inner maintain variable */
var timerHandle;
var timerObj; /* set external variable */
if ( fnAlarmIn )
{
fnAlarm = fnAlarmIn;
} if ( options && options.timeout )
{
timeout = options.timeout;
} return {
start: function(){
timerHandle = setTimeout.call(this, function(){
$("#timer_msg").printMsg(this.getTimerID() + "-->timer id");
fnAlarm.call(this);
delete this.timerHandle;
},timeout);
$("#timer_msg").printMsg(timerHandle.toString() + "-timer started");
},
stop: function(){
clearTimeout(timerHandle)
delete this.timerHandle;
$("#timer_msg").printMsg(timerHandle.toString() + "-timer stoped");
},
getTimerID: function(){
return timerHandle;
}
};
} var timer1 = createTimer();
timer1.start();
timer1.stop(); var timer2 = createTimer(function(){
$("#timer_msg").printMsg("timer custom alarm");
}, {timeout:2000});
timer2.start(); var timer3 = createTimer(function(){
$("#timer_msg").printMsg(timer3.getTimerID()+"-timer custom alarm");
}, {timeout:3000});
timer3.start(); var timer4 = createTimer(function(){
$("#timer_msg").printMsg(this.getTimerID()+"-timer custom alarm");
}, {timeout:4000});
timer4.start();
</script>
</body>
</html>

除了上面 的触发函数的对象绑定功能,

setTimeout执行函数带扩展参数,解决IE6、7不支持扩展参数的缺陷, 也是用闭包功能,见:

https://developer.mozilla.org/zh-CN/docs/DOM/window.setTimeout

上一篇:gdb调试coredump文件


下一篇:python基础语法(四)