easejs

Classical Object-Oriented Framework

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 things26, 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.27

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

Each method call, unless optimized away by the engine, is equivalent to two
function invocations, which cuts down on the available stack space.

The method wrapping may complicate tail call optimization, depending on
the JavaScript engine’s implementation and whether or not it will optimize
across the stack, rather than just a single-depth recursive call.

As such, for operations that are highly dependent on stack space, one may
wish to avoid method calls and call functions directly.

There is a very slight performance hit (though worrying about this is likely
to be a micro-optimization in the majority of circumstances).

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.28 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.29

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:

All wrappers perform a context lookup, binding to the instance’s private
visibility object of the class that defined that particular method.

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.

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:

Private members are only ever accessible from within the context of the
private member object, which is always the context when executing a method.

Private methods cannot be overridden, as they cannot be inherited.

Consequently:

We do not need to perform a context lookup: we are already in the proper
context.

We do not need to restore the context, as we never needed to change it to
begin with.

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.

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.