Next: , Previous: The Visibility Object, Up: Visibility Implementation


B.2.4 Method Wrapping

The visibility object (see The Visibility Object) is a useful tool for organizing the various members, but we still need some means of binding it to a method call. This is accomplished by wrapping each method in a closure that, among other things1, uses apply() to forward the arguments to the method, binding this to the appropriate visibility object. This is very similar to the ES5 Function.bind() call.

The following example demonstrates in an overly-simplistic way how ease.js handles class definitions and method wrapping.2

/**
 * Simple function that returns a prototype ("class"), generated from the
 * given definition and all methods bound to the provided visibility object
 */
function createClass( vis, dfn )
{
    var C      = function() {},
        hasOwn = Object.hasOwnProperty;

    for ( name in dfn )
    {
        // ignore any members that are not part of our object (further down
        // the chain)
        if ( hasOwn.call( dfn, name ) === false )
        {
            continue;
        }

        // simply property impl (WARNING: copies by ref)
        if ( typeof dfn[ name ] !== 'function' )
        {
            C.prototype[ name ] = dfn[ name ];
            continue;
        }

        // enclose name in a closure to preserve it (otherwise it'll contain
        // the name of the last member in the loop)
        C.prototype[ name ] = ( function( mname )
        {
            return function()
            {
                // call method with the given argments, bound to the given
                // visibility object
                dfn[ mname ].apply( vis, arguments );
            };
        } )( name );
    }

    return C;
};

var vis = { _data: "foo" },

    Foo = createClass( vis,
    {
        getData: function()
        {
            return this._data;
        },
    } );

var inst = new Foo();

// getData() will be bound to vis and should return its _data property
inst.getData(); // "foo"

Figure B.18: Basic "class" implementation with method binding

There are some important considerations with the implementation in Figure B.18, as well as ease.js's implementation:

As mentioned previously, each visibility object is indexed by class identifier (see Visibility Object Implementation). The appropriate visibility object is bound dynamically on method invocation based on the matching class identifier. Previously in this discussion, it was not clear how this identifier was determined at runtime. Since methods are shared by reference between subtypes, we cannot store a class identifier on the function itself.

The closure that wraps the actual method references the arguments that were passed to the function that created it when the class was defined. Among these arguments are the class identifier and a lookup method used to determine the appropriate visibility object to use for binding.3 Therefore, the wrapper closure will always know the appropriate class identifier. The lookup method is also passed this, which is bound to the instance automatically by JavaScript for the method call. It is on this object that the visibility objects are stored (non-enumerable; see Instance Memory Considerations), indexed by class identifier. The appropriate is simply returned.

If no visibility object is found, null is returned by the lookup function, which causes the wrapper function to default to this as determined by JavaScript, which will be the instance that the method was invoked on, or whatever was bound to the function via a call to call() or apply(). This means that, currently, a visibility object can be explicitly specified for any method by invoking the method in the form of: ‘inst.methodName.apply( visobj, arguments )’, which is consistent with how JavaScript is commonly used with other prototypes. However, it should be noted that this behavior is undocumented and subject to change in future releases unless it is decided that this implementation is ideal. It is therefore recommended to avoid using this functionality for the time being.4

B.2.4.1 Private Method Performance

A special exception to GNU ease.js' method wrapping implementation is made for private methods. As mentioned above, there are a number of downsides to method wrapping, including effectively halving the remaining stack space for heavily recursive operations, overhead of closure invocation, and thwarting of tail call optimization. This situation is rather awkward, because it essentially tells users that ease.js should not be used for performance-critical invocations or heavily recursive algorithms, which is very inconvenient and unintuitive.

To eliminate this issue for the bulk of program logic, method wrapping does not occur on private methods. To see why it is not necessary, consider the purpose of the wrappers:

  1. All wrappers perform a context lookup, binding to the instance's private visibility object of the class that defined that particular method.
  2. This context is restored upon returning from the call: if a method returns this, it is instead converted back to the context in which the method was invoked, which prevents the private member object from leaking out of a public interface.
  3. In the event of an override, this.__super is set up (and torn down).

There are other details (e.g. the method wrapper used for method proxies), but for the sake of this particular discussion, those are the only ones that really matter. Now, there are a couple of important details to consider about private members:

Consequently:

  1. We do not need to perform a context lookup: we are already in the proper context.
  2. We do not need to restore the context, as we never needed to change it to begin with.
  3. this.__self is never applicable.

This is all the more motivation to use private members, which enforces encapsulation; keep in mind that, because use of private members is the ideal in well-encapsulated and well-factored code, ease.js has been designed to perform best under those circumstances.


Footnotes

[1] The closure also sets the __super() method reference, if a super method exists, and returns the instance if this is returned from the method.

[2] ease.js, of course, generates its own visibility objects internally. However, for the sake of brevity, we simply provide one in our example.

[3] See lib/MethodWrappers.js for the method wrappers and ClassBuilder.getMethodInstance() for the lookup function.

[4] One one hand, keeping this feature is excellent in the sense that it is predictable. If all other prototypes work this way, why not “classes” as created through ease.js? At the same time, this is not very class-like. It permits manipulating the internal state of the class, which is supposed to be encapsulated. It also allows bypassing constructor logic and replacing methods at runtime. This is useful for mocking, but a complete anti-pattern in terms of Classical Object-Oriented development.