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
|
mocking, but a complete anti-pattern in terms of Classical Object-Oriented
|
||||||
development.}
|
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
|
@node Pre-ES5 Fallback
|
||||||
@subsection Pre-ES5 Fallback
|
@subsection Pre-ES5 Fallback
|
||||||
For any system that is to remain functionally compatible across a number of
|
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
|
called. Instead, we would have to override the public @var{walk} method to
|
||||||
prevent our dog from moving his front feet.
|
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
|
@subsection Protected Members
|
||||||
Protected members are often misunderstood. Many developers will declare all
|
Protected members are often misunderstood. Many developers will declare all
|
||||||
of their members as either public or protected under the misconception that
|
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;
|
dest[ name ] = value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -67,7 +67,11 @@ require( 'common' ).testCase(
|
||||||
|
|
||||||
// stub factories used for testing
|
// stub factories used for testing
|
||||||
var stubFactory = this.require( 'MethodWrapperFactory' )(
|
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
|
// used for testing proxies explicitly
|
||||||
|
@ -282,4 +286,45 @@ require( 'common' ).testCase(
|
||||||
this.members[ 'public' ].foo();
|
this.members[ 'public' ].foo();
|
||||||
this.assertOk( called, "Override unkept" );
|
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