input:

Log:

Zero

Fine grained, no-delay scheduler using postMessage().

Bypasses default browser system throttling (typically 5 to 10 msec).

David Baron:

setZeroTimeout (fn)

GitHub:

setZeroTimeout.js

Reentrant setTimeout() within Object constructor

Keyword this

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.

Easy

Privileged loop method is exposed, and each class instance has its own copy.
  this.loop = function() {
    id = setTimeout(
      function() {
        this.loop();
        client( this );
      }.bind( this ),
      delay
    );
  }

Compact

Public loop is exposed, but defined only once for the prototype.
  Cf.prototype.loop =
  function(){
    this.id = setTimeout(
      function() {
        this.loop();
        this.client( this );
      }.bind( this ),
      this.delay
    );
  };

Secure

Private loop is hidden, but each instance has a copy.
  var self = this;
  function loop() {
    id = setTimeout(
      function() {
        loop();
        client( self );
      },
      delay
    );
  }

IIFE class prototype

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.

Limited privacy

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.

IIFE with public validator and private key

 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
   };
 })();

Accessors

This variant merely attempts to address popular criticism that accessors provide false privacy, employing a simple trick.

Holub 2003

Yegor 2014

Security?

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.

Results:

Allocation size & speed

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.

Invocation rates

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.

Winners

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.

Pro Tip

To fully reload all JS source files, hold the shift key.

Next steps

• Run invocation test on setZeroTimeout().

• How well do multiple schedules interleave at scale?