Log:
Bypasses default browser system throttling (typically 5 to 10 msec).
David Baron:
GitHub:
The setup:
loop = function() { id = setTimeout( function() { loop(); client( this ); }, delay ); }The closure for this is broken.
Crockford says: var that=this; "...to make the object available to the private methods. This is a workaround for an error in the ECMAScript Language Specification which causes 'this' to be set incorrectly for inner functions."
(At first pass, I would prefer var scope=this within a constructor function, as a more meaningful moniker for someone coming from a C++ class development background. But that's still not quite right.)
this context object
In JS, this points to the context in which a function executes, which just happens to be as expected within an object constructor function. If a method is passed around (a callback), the original context gets lost. Because the constructor product is fundamentally an Object, not a function scope, I settled on var self=this in the one necessary case that I identify.
(A number of Crockford's criticisms of the JS language seem to be stemming from an effort to force C++ class paradigms onto JS. These simple workarounds are muddling the true differences between the languages, and the internet is flooded with thousands of articles, tutorials and QA pages trying to clarify.)
Memory Efficiency versus Privacy
To solve the broken closure in the setup above, we should take into account the purpose and performance requirements of the specific class being defined.
The flexibility of JS syntax permits a myriad tradeoffs to be considered in planning Object instantiation. These include efficiency for large numbers of Objects, and privacy for security and reliability of modules.
A third and frequently important priority is simplicity, for ease of writing and understanding, typically less efficient and less secure.
this.loop = function() { id = setTimeout( function() { this.loop(); client( this ); }.bind( this ), delay ); }
Cf.prototype.loop = function(){ this.id = setTimeout( function() { this.loop(); this.client( this ); }.bind( this ), this.delay ); };
var self = this; function loop() { id = setTimeout( function() { loop(); client( self ); }, delay ); }
Showing details of the loop method within its containing class structure:
function Cf( delay, client ) { this.delay = delay; this.client = client; this.id = 0; } Cf.prototype = (function() { function loop() { this.id = setTimeout( function() { loop.call( this ); this.client( this ); }.bind( this ), this.delay ); } function control() { ... control logic loop.call( this ); } return { control: control }; })();The constructor function can now be used as a regular class with exposed prototype methods:
var i = new Cf( args ); i.control();
Rather than aliasing this to a private variable, the pattern shown here makes heavy use of .call() and .bind() to retain the context object. Note that any variables defined in the prototype scope are shared with all instances, so aliasing here is not permitted.
All instances share one prototype definition with hidden methods, invoked automatically at startup.
While prototype methods can be hidden, they cannot access instance variables that are hidden in the constructor function. Therefore, client parameters (and timeout id) remain exposed.
function Cf() { var id = 0; this.set_id = function( key, new_id ) { if( this.valid( key ) ) { id = new_id; } } } Cf.prototype = (function() { var pvt_key = random(); function valid( key ) { return( key == pvt_key ); } function loop() { this.set_id( pvt_key, setTimeout(...) ); } return { valid: valid, control: control }; })();
This variant merely attempts to address popular criticism that accessors provide false privacy, employing a simple trick.
Any client code can add a member function to access the prototype key and 'break in'.
Real security design issues are beyond the scope of this examination.
Easy:
262 bytes, 9 ms/100K
Compact:
58 bytes, 2.4 ms/100K
Secure:
344 bytes, 14 ms/100K
IIFE:
58 bytes, 2 ms/100K
Keyed:
256 bytes, 5 ms/100K
• Allocation size and speed proportions are as expected from theory.
Easy, Compact, IIFE, Keyed: Safari OSx
450 - 500 calls/ms
Secure: Safari OSx
700 - 750 calls/ms
Chrome, Chromium:
80 - 150 calls/ms
other, iOS:
100 - 200 calls/ms
• Invocation rate proportions are interesting, and raise further questions.
For most use cases, Secure pattern with self=this is ideal, with reduced runtime footprint and maximum privacy.
An application involving fine grained instance recycling would benefit from IIFE over Compact, with the added benefit of method privacy.
To fully reload all JS source files, hold the shift key.
• Run invocation test on setZeroTimeout().
• How well do multiple schedules interleave at scale?