I use this Timer
function quite a bit lately for all sorts of stuff,
and would appreciate if someone could review/analyze/criticize/verify
and, of course, suggest things I can do to optimize it for high
frequency execution (mainly animation).
My goal was to replicate ActionScript's counterpart (Timer
class) and its usage simplicity: registering fns for timer's event dispatch, starting/stopping/resetting the timer, etc.
//
//
;( function ( _host_, _aproto ) {
var t = true,
f = false,
nl = null,
timerEvent = {
start : "timer-start",
timer : "timer",
stop : "timer-stop",
end : "timer-end"
},
un, // === undefined
_tm;
// Array.prototype extensions helpers:
// .each() .keep() .gc() .has() .empty() .substitute()
_aproto.each = function ( fn, flgIterBw ) {
var len = this.length,
i;
if ( flgIterBw !== t ) {
for (
i = 0;
i < len;
i++
) {
if ( fn.call( this, i, this[i] ) === f ) break;
}
} else {
for (
i = len;
--i >= 0;
) {
if ( fn.call( this, i, this[i] ) === f ) break;
}
}
return this;
};
_aproto.keep = function ( fn ) {
return this.each(
function ( i, o ) {
( fn.call( this, i, o ) === t )
|| this.splice( i, 1 );
},
t
);
};
_aproto.gc = function () {
var toremove = slc( arguments );
return this.each(
function ( i, o ) {
toremove.has( o )
&& this.splice( i, 1 );
},
t
);
};
_aproto.has = function ( v ) {
return this.indexOf( v ) !== -1;
};
_aproto.empty = function () {
return ( this.length = 0, this );
};
_aproto.substitute = function ( arr ) {
return ( _aproto.push.apply( this.empty(), arr ), this );
};
// helper fns
function isobj( o ) {
return o === Object( o );
}
function isplainobj( o ) {
return Object.prototype.toString.call( o ) === "[object Object]";
}
function isfn( o ) {
return typeof o === "function";
}
function isvalid( o ) {
return o !== un
&& o !== nl
&& ( o === o );
}
function owns( obj, p ) {
return obj.hasOwnProperty( p );
}
// loops objects own properties
// breaks if fn return false
function owneach( obj, fn ) {
if (
isobj( obj )
&& isfn( fn )
) {
for ( var p in obj ) {
if ( owns( obj, p ) ) {
if ( fn.call( obj, p, obj[p] ) === f ) break;
}
}
}
return obj;
}
// attaches set of properties to an object
function rig_props( obj, props ) {
if ( isobj( obj )
&& isplainobj( props )
) {
owneach(
props,
function ( p, v ) {
obj[p] = v;
}
);
}
return obj;
}
function slc( arg, i, j ) {
return Array.prototype.slice.call( arg, i, j );
}
function vacate( obj ) {
for ( var p in obj ) {
owns( Object.prototype, p )
|| ( delete obj[p] );
}
return obj;
}
// 'asyncs' a function
function defer( fn ) {
var args1 = slc( arguments, 1 );
return function () {
var args = args1.concat( slc( arguments ) ),
target = this,
origfn = fn;
setTimeout(
function () {
return origfn.apply( target, args );
}
);
return this;
};
}
// gives an object basic event handling support
// .addListener() .removeListener() .triggerEvent()
function listener( obj ) {
if (
isobj( obj )
) {
var handlers = {};
rig_props(
obj,
{
// registers set of fns for an event 'e'
addListener : function ( e ) {
if (
isvalid( e )
) {
var fnargs =
slc( arguments, 1 )
.keep(
function ( i, o ) {
return isfn( o );
}
);
owns( handlers, e )
&& (
_aproto.push.apply(
handlers[ e ],
fnargs
),
t
)
|| ( handlers[ e ] = slc( fnargs ) );
}
return obj;
},
// removes fns registered for 'e' event
removeListener : function ( e ) {
if (
isvalid( e )
) {
if ( owns( handlers, e ) ) {
var fnargs =
slc( arguments, 1 )
.keep(
function ( i, o ) {
return isfn( o );
}
);
fnargs.length
&& (
_aproto.gc.apply(
handlers[ e ],
fnargs
),
handlers[ e ].length
|| ( delete handlers[ e ] ),
t
)
|| (
handlers[ e ].empty(),
delete handlers[ e ]
);
}
} else {
owneach(
handlers,
function ( evt, fns ) {
fns.empty();
}
);
vacate( handlers );
}
return obj;
},
// runs fns registered for evt 'e'
triggerEvent : function ( e ) {
if (
isvalid( e )
) {
if (
owns( handlers, e )
) {
var fireargs = slc( arguments, 1 );
handlers[ e ]
.each(
function ( k, evhandler ) {
defer( evhandler )
.call(
obj,
{
type : e,
data : fireargs,
target : obj,
handler : evhandler
}
);
}
);
}
}
return obj;
}
}
);
}
return obj;
}
//
// declares Timer factory fn
_tm = function ( delay, repeatCount ) {
return ( function ( delay, fireNTimes ) {
var
// timer obj
host = this,
// timer's private state
// used/manipulated by api bellow
timerState = {
'current-count' : 0,
'delay' : Math.abs( parseFloat( delay ) ) || 1000,
'repeat-count' : Math.abs( parseInt( fireNTimes ) ) || Infinity,
'running' : f,
'interval' : un
},
// arguments provided to timer's .start() method
// used as args for triggered fns
fireargs = [];
// attaches api to timer obj
// .start() .stop() .reset() .currentCount() .delay() .repeatCount() .running() .state()
rig_props(
host,
{
// starts timer event dispatch
// sets provided args as
// parameters to triggered fns
// triggers 'timer-start' event
// and 'timer' events
start : function () {
var startargs;
host.running()
|| (
timerState.running = t,
( startargs = slc( arguments ) ).length
&& fireargs.substitute( startargs ),
host.triggerEvent.apply(
host,
[ timerEvent.start ]
.concat( fireargs )
),
timerState['current-count'] += 1,
host.triggerEvent.apply(
host,
[ timerEvent.timer ]
.concat( fireargs )
),
( timerState['current-count'] === timerState['repeat-count'] )
&& host.reset()
|| ( timerState.interval =
setInterval(
function () {
( timerState['current-count'] < timerState['repeat-count'] )
&& (
timerState['current-count'] += 1,
host.triggerEvent.apply(
host,
[ timerEvent.timer ]
.concat( fireargs )
),
( timerState['current-count'] === timerState['repeat-count'] )
&& host.reset()
);
},
timerState.delay
)
)
);
return host;
},
// pauses triggering timer events
// triggers 'timer-stop' event
stop : function () {
host.running()
&& (
( timerState.interval !== un )
&& (
clearInterval( timerState.interval ),
timerState.interval = un
),
timerState.running = f,
host.triggerEvent.apply(
host,
[ timerEvent.stop ]
.concat( fireargs )
)
);
return host;
},
// nulls timer state
// triggers 'timer-end' event
reset : function () {
( timerState.interval !== un )
&& (
clearInterval( timerState.interval ),
timerState.interval = un
);
timerState.running = f;
timerState["current-count"] = 0;
host.triggerEvent.apply(
host,
[ timerEvent.end ]
.concat( fireargs )
);
return host;
},
// how many times timer fired
currentCount : function () {
return timerState['current-count'];
},
// return timer's fire rate in ms
delay : function () {
return timerState.delay;
},
// how many times timer will fire 'timer' event
repeatCount : function () {
return timerState['repeat-count'];
},
// returns boolean
running : function () {
return timerState.running;
},
// returns timers intrnal state{}
state : function () {
return {
currentCount : timerState['current-count'],
delay : timerState.delay,
repeatCount : timerState['repeat-count'],
running : timerState.running
};
}
}
);
return host;
} ).call( listener( {} ), delay, repeatCount );
};
//
// attaches Timer fn to global scope
_host_.Timer = _tm;
} )( self, Array.prototype );
//
// use:
//
// var
// tm = Timer( 1000/50 ); // set timers fq to 50 times a sec
//
// // register fns for 'timer' event
// tm.addListener(
// "timer",
// function () { console.log( arguments ) },
// doStuff1,
// doStuff2
// );
//
// someElement.onmouseover = function () { tm.start( someElement ); };
// someElement.onmouseout = function () { tm.stop(); };
// someElement.onclick = function () { tm.reset(); };
//
// etc.
//