Private method wrapping eliminated
This has wonderful consequences; see branch commits for more information.perfodd
commit
9c70904c39
|
@ -1469,6 +1469,65 @@ constructor logic and replacing methods at runtime. This is useful for
|
|||
mocking, but a complete anti-pattern in terms of Classical Object-Oriented
|
||||
development.}
|
||||
|
||||
@subsubsection 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:
|
||||
|
||||
@enumerate
|
||||
@item
|
||||
All wrappers perform a context lookup, binding to the instance's private
|
||||
visibility object of the class that defined that particular method.
|
||||
@item
|
||||
This context is restored upon returning from the call: if a method returns
|
||||
@var{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.
|
||||
@item
|
||||
In the event of an override, @var{this.__super} is set up (and torn down).
|
||||
@end enumerate
|
||||
|
||||
There are other details (e.g. the method wrapper used for @ref{Method
|
||||
Proxies,,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:
|
||||
|
||||
@itemize
|
||||
@item
|
||||
Private members are only ever accessible from within the context of the
|
||||
private member object, which is always the context when executing a method.
|
||||
@item
|
||||
Private methods cannot be overridden, as they cannot be inherited.
|
||||
@end itemize
|
||||
|
||||
Consequently:
|
||||
|
||||
@enumerate
|
||||
@item
|
||||
We do not need to perform a context lookup: we are already in the proper
|
||||
context.
|
||||
@item
|
||||
We do not need to restore the context, as we never needed to change it to
|
||||
begin with.
|
||||
@item
|
||||
@var{this.__self} is never applicable.
|
||||
@end enumerate
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@node Pre-ES5 Fallback
|
||||
@subsection Pre-ES5 Fallback
|
||||
For any system that is to remain functionally compatible across a number of
|
||||
|
|
|
@ -337,6 +337,10 @@ method}, with the name @var{_moveFrontLeg}. The old method will still be
|
|||
called. Instead, we would have to override the public @var{walk} method to
|
||||
prevent our dog from moving his front feet.
|
||||
|
||||
Note that GNU ease.js is optimized for private member access; see
|
||||
@ref{Property Proxies,,Property Proxies} and @ref{Method
|
||||
Wrapping,,Method Wrapping} for additional details.
|
||||
|
||||
@subsection Protected Members
|
||||
Protected members are often misunderstood. Many developers will declare all
|
||||
of their members as either public or protected under the misconception that
|
||||
|
|
|
@ -173,9 +173,12 @@ exports.buildMethod = function(
|
|||
}
|
||||
|
||||
}
|
||||
else if ( keywords[ 'abstract' ] )
|
||||
else if ( keywords[ 'abstract' ] || keywords[ 'private' ] )
|
||||
{
|
||||
// we do not want to wrap abstract methods, since they are not callable
|
||||
// we do not want to wrap abstract methods, since they are not
|
||||
// callable; further, we do not need to wrap private methods, since
|
||||
// they are only ever accessible when we are already within a
|
||||
// private context (see test case for more information)
|
||||
dest[ name ] = value;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -67,7 +67,11 @@ require( 'common' ).testCase(
|
|||
|
||||
// stub factories used for testing
|
||||
var stubFactory = this.require( 'MethodWrapperFactory' )(
|
||||
function( func ) { return func; }
|
||||
function( func )
|
||||
{
|
||||
// still wrap it so that the function is encapsulated
|
||||
return function() { return func() };
|
||||
}
|
||||
);
|
||||
|
||||
// used for testing proxies explicitly
|
||||
|
@ -282,4 +286,45 @@ require( 'common' ).testCase(
|
|||
this.members[ 'public' ].foo();
|
||||
this.assertOk( called, "Override unkept" );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* This is a beautiful consequence of the necessay context in which
|
||||
* private methods must be invoked.
|
||||
*
|
||||
* If a method has the ability to call a private method, then we must
|
||||
* already be within a private context (that is---using the private
|
||||
* member object, which happens whenever we're executing a method of
|
||||
* that class). The purpose of the method wrapper is to (a) determine
|
||||
* the proper context, (b) set up super method references, and (c)
|
||||
* restore the context in the event that the method returns `this'. Not
|
||||
* a single one of these applies: (a) is void beacuse we are already in
|
||||
* the proper context; (b) is not applicable since private methods
|
||||
* cannot have a super method; and (c) we do not need to restore context
|
||||
* before returning because the context would be the same (per (a)).
|
||||
*
|
||||
* That has excellent performance implications: not only do we reduce
|
||||
* class building times for private methods, but we also improve method
|
||||
* invocation times, since we do not have to invoke a *closure* for each
|
||||
* and every method call. Further, recursive private methods are no
|
||||
* longer an issue since they do not gobble up the stack faster and,
|
||||
* consequently, the JavaScript engine can now take advantage of tail
|
||||
* call optimizations.
|
||||
*
|
||||
* This is also further encouragement to use private members. :)
|
||||
*/
|
||||
'Private methods are not wrapped': function()
|
||||
{
|
||||
var f = function() {},
|
||||
name = 'foo';
|
||||
|
||||
this.sut.buildMethod(
|
||||
this.members, {}, name, f, { 'private': true },
|
||||
function() {}, 1, {}
|
||||
);
|
||||
|
||||
// if the private method was not wrapped, then it should have been
|
||||
// assigned to the member object unencapsulated
|
||||
this.assertStrictEqual( this.members[ 'private' ][ name ], f );
|
||||
},
|
||||
} );
|
||||
|
|
Loading…
Reference in New Issue