使用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
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)