coope/sec/encap-hacks.tex

484 lines
26 KiB
TeX

\section{Encapsulating the Hacks}
\label{sec:encap-hacks}
Imagine jumping into a project in order to make a simple modification and then
seeing the code in \jsref{lst:prot-share}. This is a far cry from the simple
protected member declarations in traditional classical object-oriented
languages. In fact, there becomes a point where the hacks discussed in the
previous sections become unmaintainable messes that add a great deal of
boilerplate code with little use other than to distract from the actual
software itself.
However, we do not have to settle for those messy implementations. Indeed, we
can come up with some fairly elegant and concise solutions by encapsulating the
hacks we have discussed into a classical object-oriented framework, library or
simple helper functions. Let's not get ahead of ourselves too quickly; we will
start exploring basic helper functions before we deal with diving into a full,
reusable framework.
This section is intended for educational and experimental purposes. Before using
these examples to develop your own class system for ECMAScript, ensure that none
of the existing systems satisfy your needs; your effort is best suited toward
the advancement of existing projects than the segregation caused by the
introduction of additional, specialty frameworks.\footnote{That is not to
discourage experimentation. Indeed, one of the best, most exciting and fun ways
to learn about these concepts are to implement them yourself.} These are
discussed a bit later.
\subsection{Constructor/Prototype Factory}
\label{sec:ctor-factory}
Section~\ref{sec:extending} offered one solution to the problem of creating an
extensible constructor, allowing it to be used both to instantiate new objects
and as a prototype. Unfortunately, as \jsref{lst:ctor-extend} demonstrated, the
solution adds a bit of noise to the definition that will also be duplicated for
each constructor. The section ended with the promise of a cleaner, reusable
implementation. Perhaps we can provide that.
Consider once again the issue at hand. The constructor, when called
conventionally with the \operator{new} operator to create a new instance, must
perform all of its construction logic. However, if we wish to use it as a
prototype, it is unlikely that we want to run \emph{any} of that logic --- we
are simply looking to have an object containing each of its members to use as a
prototype without the risk of modifying the prototype of the constructor in
question. Now consider how this issue is handled in other classical languages:
the \keyword{extend} keyword.
ECMAScript has no such keyword, so we will have to work on an implementation
ourselves. We cannot use the name \func{extend()}, as it is a reserved
name;\footnote{Perhaps for future versions of ECMAScript.} as such, we will
start with a simple \func{Class} factory function with which we can create new
``classes'' without supertypes. We can than provide a \func{Class.extend()}
method to define a ``class'' \emph{with} a supertype.
\lstinputlisting[%
label=lst:ctor-factory,
caption=Constructor factory,
lastline=60,
]{lst/ctor-factory.js}
\jsref{lst:ctor-factory} demonstrates one such possible implementation of a
constructor factory. Rather than thinking of ``creating a class'' and ``creating
a class with a supertype'' as two separate processes, it is helpful to consider
them one and the same; instead, we can consider the former to be ``creating a
class \emph{with an empty supertype}''. As such, invoking \func{Class()} simply
calls \func{Class.extend()} with \keyword{null} for the base (on line 6),
allowing \func{Class.extend()} to handle the creation of a new constructor
without a supertype.
Both \func{Class()} and \func{Class.extend()} accept a \var{dfn} argument, which
we will refer to as the \dfn{definition object}; this object is to contain each
member that will appear on the prototype of the new constructor. The \var{base}
parameter, defined on \func{Class.extend()}, denotes the constructor from which
to extend (the constructor that will be instantiated and used as the prototype).
Line 11 will default \var{base} to an empty function if one has not been
provided (mainly, to satisfy the \func{Class()} call on line 6).
With that, we can now continue onto creating our constructor, beginning on line
16. Section~\ref{sec:extending} introduced the concept of using an
\var{extending} flag to let the constructor know when to avoid all of its
construction logic if being used only as a prototype (see
\jsref{lst:ctor-extend}). The problem with this implementation, as discussed,
was that it required that \emph{each} constructor that wishes to use this
pattern implement it themselves, violating the DRY\footnote{``Don't repreat
yourself'', \emph{The Pragmatic Programmer}.} principle. There were two main
areas of code duplication in \jsref{lst:ctor-extend} --- the checking of the
\var{extending} flag in the constructor and the setting (and resetting) of the
flag in \func{F.asPrototype()}. In fact, we can eliminate the
\func{asPrototype()} method altogether once we recognize that its entire
purpose is to set the flags before and after instantiation.
To address the first code duplication issue --- the checking of the flag in the
constructor --- we must remove the need to perform the check manually for each
and every constructor. The solution, as demonstrated in
\jsref{lst:ctor-factory}, is to separate our generic constructor logic (shared
between all constructors that use the factory) from the logic that can vary
between each constructor. \var{ctor} on line 16 accomplishes this by first
performing the \var{extending} check (lines 19--22) and then forwarding all
arguments to a separate function (\func{\_\_construct()}), if defined, using
\func{Function.apply()} (lines 25--28). One could adopt any name for the
constructor method; it is not significant.\footnote{The \code{\_\_construct}
name was taken from PHP.} Note that the first argument to
\func{Function.apply()} is important, as it will ensure that \keyword{this} is
properly bound within the \func{\_\_construct()} method.
To address the second code duplication issue and remove the need for
\func{asPrototype()} in \jsref{lst:ctor-extend} entirely, we can take advantage
of the implications of \func{Class.extend()} in \jsref{lst:ctor-factory}. The
only time we wish to use a constructor as a prototype and skip
\func{\_\_construct()} is during the process of creating a new constructor. As
such, we can simply set the \var{extending} flag to \keyword{true} when we begin
creating the new constructor (see line 14, though this flag could be placed
anywhere before line 31) and then reset it to \keyword{false} once we are done
(line 38). With that, we have eliminated the code duplication issues associated
with \jsref{lst:ctor-extend}.
The remainder of \jsref{lst:ctor-factory} is simply an abstraction around the
manual process we have been performing since section~\ref{sec:proto} --- setting
the prototype, properly setting the constructor and extending the prototype
with our own methods. Recall section~\ref{sec:prot} in which we had to manually
assign each member of the prototype for subtypes in order to ensure that we did
not overwrite the existing prototype members (e.g. \func{M.prototype.push()} in
\jsref{lst:prot-share}). The very same issue applies here: Line 31 first sets
the prototype to an instance of \var{base}. If we were to then set
\code{ctor.prototype = dfn}, we would entirely overwrite the benefit gained from
specifying \var{base}. In order to automate this manual assignment of each
additional prototype member of \var{dfn}, \func{copyTo()} is provided, which
accepts two arguments --- a destination object \var{dest} to which each given
member of \var{members} should be copied (defined on line 43 and called on line
34).
Like the examples provided in section~\ref{sec:hack-around}, we
use a self-executing function to hide the implementation details of our
\func{Class} function from the rest of the world.
To demonstrate use of the constructor factory, \jsref{lst:ctor-factory-ex}
defines two classes\footnote{The reader should take care in noting that the term
``class'', as used henceforth, will refer to a class-like object created using
the systems defined within this article. ECMAScript does not support classes, so
the use of the term ``class'' in any other context is misleading.} --- \var{Foo}
and \var{SubFoo}. Note that how, by placing the curly braces on their own line,
we can create the illusion that \func{Class()} is a language construct:
\lstinputlisting[%
label=lst:ctor-factory-ex,
caption=Demonstrating the constructor factory,
firstline=62,
firstnumber=last
]{lst/ctor-factory.js}
The reader should note that an important assertion has been omitted for brevity
in \jsref{lst:ctor-factory}. Consider, for example, what may happen in the case
of the following:
\begin{verbatim}
Class.extend( "foo", {} );
\end{verbatim}
It is apparent that \code{"foo"} is not a function and therefore cannot be used
with the \operator{new} operator. Given that, consider line 31, which blindly
invokes \code{base()} without consideration for the very probable scenario that
the user mistakenly (due to their own unfamiliarity or a simple bug) provided us
with a non-constructor for \var{base}. The user would then be presented with a
valid, but not necessarily useful error --- did the error occur because of user
error, or due to a bug in the factory implementation?
To avoid confusion, it would be best to perform a simple assertion before
invoking \var{base} (or wrap the invocation in a try/catch block, although doing
so is not recommended in case \func{base()} throws an error of its own):
\begin{verbatim}
if ( typeof base !== 'function' )
{
throw TypeError( "Invalid base provided" );
}
\end{verbatim}
Note also that, although this implementation will work with any constructor as
\var{base}, only those created with \func{Class()} will have the benefit of
being able to check the \var{extending} flag. As such, when using
\func{Class.extend()} with third-party constructors, the issue of extensible
constructors may still remain and is left instead in the hands of the developer
of that base constructor.
\subsubsection{Factory Conveniences}
Although our constructor factory described in section~\ref{sec:ctor-factory} is
thus far very simple, one should take the time to realize what a powerful
abstraction has been created: it allows us to inject our own code in any part of
the constructor creation process, giving us full control over our class-like
objects. Indeed, this abstraction will be used as a strong foundation going
forward throughout all of section~\ref{sec:encap}. In the meantime, we can take
advantage of it in its infancy to provide a couple additional conveniences.
First, consider the syntax of \func{Class.extend()} in \jsref{lst:ctor-factory}.
It requires the extending of a constructor to be done in the following manner:
\begin{verbatim}
var SubFoo = Class.extend( Foo, {} );
\end{verbatim}
Would it not be more intuitive to instead be able to extend a constructor in the
following manner?
\begin{verbatim}
var SubFoo = Foo.extend( {} );
\end{verbatim}
The above two statements are semantically equivalent --- they define a subtype
\var{SubFoo} that extends from the constructor \var{Foo} --- but the latter
example is more concise and natural. Adding support for this method is trivial,
involving only a slight addition to \jsref{sec:ctor-factory}'s \func{C.extend()}
method, perhaps around line 30:
\lstinputlisting[%
label=lst:ctor-factory-sextend,
caption=Adding a static \func{extend()} method to constructors,
firstnumber=31
]{lst/ctor-factory-sextend.js}
Of course, one should be aware that this implementation is exploitable in that,
for example, \func{Foo.extend()} could be reassigned at any point. As such,
using \func{Class.extend()} is the safe implementation, unless you can be
certain that such a reassignment is not possible. Alternatively, in ECMAScript 5
and later environments, one can use \func{Object.defineProperty()}, as discussed
in sections~\ref{sec:encap-naive} and \ref{sec:encap-proper}, to make the method
read-only.
Now consider the instantiation of our class-like objects, as was demonstrated in
\jsref{lst:ctor-factory-ex}:
\begin{verbatim}
var inst = new Foo( "Name" );
\end{verbatim}
We can make our code even more concise by eliminating the \operator{new}
operator entirely, allowing us to create a new instance as such:
\begin{verbatim}
var inst = Foo( "Name" );
\end{verbatim}
Of course, our constructors do not yet support this, but why may we want such a
thing? Firstly, for consistency --- the core ECMAScript constructors do not
require the use of the keyword, as has been demonstrated throughout this article
with the various \var{Error} types. Secondly, the omission of the keyword would
allow us to jump immediately into calling a method on an object without dealing
with awkward precedence rules: \code{Foo( "Name" ).getName()} vs. \code{( new
Foo( "Name" ) ).getName()}. However, those reasons exist more to offer syntactic
sugar; they do little to persuade those who do want or not mind the
\operator{new} operator.
The stronger argument against the \operator{new} operator is what happens should
someone \emph{omit} it, which would not be at all uncommon since the keyword is
not required for the core ECMAScript constructors. Recall that \keyword{this},
from within the constructor, is bound to the new instance when invoked with the
\operator{new} operator. As such, we expect to be able to make assignments to
properties of \keyword{this} from within the constructor without any problems.
What, then, happens if the constructor is invoked \emph{without} the keyword?
\keyword{this} would instead be bound (according to the ECMAScript
standard\cite{es5-call}) to ``the global object'',\footnote{In most browser
environments, the global object is \var{window}.} unless in strict mode. This is
dangerous:
\lstinputlisting[%
label=lst:new-global,
caption=Introducing unintended global side-effects with constructors
]{lst/new-global.js}
Consider \jsref{lst:new-global} above. Function \func{Foo()}, if invoked with
the \operator{new} operator, results in an object with a \var{Boolean} property
equal to \keyword{true}. However, if we were to invoke \func{Foo()}
\emph{without} the \operator{new} operator, this would end up \emph{overwriting
the built-in global \var{Boolean} object reference}. To solve this problem,
while at the same time providing the consistency and convenience of being able
to either include or omit the \operator{new} operator, we can add a small block
of code to our generated constructor \var{ctor} (somewhere around line 23 of
\jsref{lst:ctor-factory}, after the extend check but before
\func{\_\_construct()} is invoked):
\lstinputlisting[%
label=lst:new-global-fix,
caption=Allowing for omission of the \operator{new} operator,
firstnumber=24
]{lst/new-global-fix.js}
The check, as demonstrated in \jsref{lst:new-global-fix}, is as simple as
ensuring that \keyword{this} is properly bound to a \emph{new instance of our
constructor \var{ctor}}. If not, the constructor can simply return a new
instance of itself through a recursive call.
Alternatively, the reader may decide to throw an error instead of automatically
returning a new instance. This would require the use of the \operator{new}
operator for instantiation, while still ensuring that the global scope will not
be polluted with unnecessary values. If the constructor is in strict mode, then
the pollution of the global scope would not be an issue and the error would
instead help to point out inconsistencies in the code. However, for the reason
that the keyword is optional for many core ECMAScript constructors, the author
recommends the implementation in \jsref{lst:new-global-fix}.
\subsection{Private Member Encapsulation}
Section~\ref{sec:encap} discussed the encapsulation of private member data
by means of private property and method objects, avoiding the performance impact
of privileged members (see section~\ref{sec:privileged}). In order to avoid
memory leaks, the private data was stored on the instance itself rather than a
truly encapsulated object. The amount of code required for this implementation
is relatively small, but it is still repeated unnecessarily between all
constructors.
The private member implementation had two distinct pieces --- private
properties, as demonstrated in \jsref{lst:encap-inst}, and private methods, as
demonstrated in \jsref{lst:method-priv}. This distinction is important, as
private methods should not be redefined for each new instance (see
\fref{fig:proto-priv-cmp}). Properties, however, \emph{must} have their values
copied for each new instance to prevent references from being shared between
multiple instances (see \jsref{lst:proto-reuse}; this is not an issue for
scalars). For the time being, we will focus on the method implementation and
leave the manual declaration of private properties to the \func{\_\_construct()}
method.
The listings in section~\ref{sec:encap} were derived from a simple concept ---
the private member objects were within the scope of the prototype members.
However, if we are encapsulating this hack within our constructor factory, then
the members (the definition object) would be declared \emph{outside} the scope
of any private member objects that are hidden within our factory. To expose the
private ``prototype'' object, we could accept a function instead of a definition
object, which would expose a reference to the object (\jsref{lst:prot-func}).
However, this would be very unnatural and unintuitive. To keep our ``class''
declarations simple, another method is needed.
Consider the private member concept in a classical sense --- the data should be
available only to the methods of the class, but should not be accessible outside
of them. That is, given any class \code{C} with private property \code{C.\_priv}
and public method \code{C.getPrivValue()}, and an instance \code{i} of class
\code{C}, \code{i.\_priv} should not be defined unless within the context of
\code{i.getPrivValue()}. Consider then the only means of exposing that data to
the members of the prototype in ECMAScript without use of closures: through the
instance itself (\keyword{this}). This naturally derives an implementation that
had not been previously considered due to the impracticality of its use without
an automated factory --- exposing private members before a method invocation and
revoking them after the method has returned.
To accomplish this, the factory must be able to intelligently determine when a
method is being invoked. This leads us into a somewhat sensitive topic ---
function wrapping. In order to perform additional logic on invocation of a
particular method, it must be wrapped within another function. This
\dfn{wrapper} would expose the private data on \keyword{this}, invoke the
original function associated with the method call, remove the reference and then
return whatever value was returned by the original function. This creates the
illusion of invoking the method directly.\footnote{This is the same concept used
to emulate \code{Function.bind()} in pre-ECMAScript 5 environments. This concept
can also be easily extended to create \dfn{partially applied functions}.}
\lstinputlisting[%
label=lst:func-wrap,
caption=Wrapping a function by returning a \emph{new} function which calls the
original,
lastline=16
]{lst/func-wrap.js}
\jsref{lst:func-wrap} demonstrates the basic concept of a function wrapper.
\func{wrap()} accepts a single argument, \var{func}, and returns a new anonymous
function which invokes \var{func}, returning its value with a prefix and suffix.
Note how all arguments are forwarded to \var{func}, allowing us to invoke our
wrapped function as if it were the original. Also note the context in which
\var{func} is being called (the first argument of \func{apply()}). By binding
\keyword{this} of \var{func} to \keyword{this} of our wrapper, we are
effectively forwarding it. This detail is especially important if we are using
a wrapper within a prototype, as we \emph{must} bind \keyword{this} to the
instance that the method is being invoked upon. Use of \func{wrap()} with a
prototype is demonstrated in \jsref{lst:func-wrap-ex} below.
\lstinputlisting[%
label=lst:func-wrap-ex,
caption=Using \func{wrap()} from \jsref{lst:func-wrap} with prototypes,
firstnumber=last,
firstline=20
]{lst/func-wrap.js}
It is this concept that will be used to implement method wrapping in our
constructor factory. For each function $f$ of definition object $D$, $f'$ will
be created using a method similar to \jsref{lst:func-wrap-ex}. $f'$ will invoke
$f$ after setting the private member object on \keyword{this}, then reset it
after $f$ returns. Finally, the return value of $f$ will be returned by $f'$. It
should be noted that $f'$ must exist even if $f$ is public, since public methods
may still need access to private members.\footnote{As we will see in the
examination of \fref{fig:func-wrap-perf}, the performance impact of this
decision is minimal.}
\begin{figure*}[t]
\center
\subfloat[%
Wrapper performance \emph{(invocation only)}. Operations per second rounded to
millions.\cite{jsperf-func-wrap} Numbers in parenthesis indicate percent
change between the two values, indicating a significant performance loss.
]{
\input{data/func-wrap-invoke.tex}
\label{fig:func-wrap-perf-invoke}
}
\quad
\subfloat[%
Wrapper performance \emph{with business logic}
(\code{(new Array(100)).join(',|').split('|')}); performance
impact is negligible. Operations per second.\cite{jsperf-func-wrap-blogic}
]{
\input{data/func-wrap-blogic.tex}
\label{fig:func-wrap-perf-blogic}
}
\caption{Function wrapping performance considerations. When measuring invocation
performance, the wrapper appears to be a terrible solution to any problem.
However, when considering the business logic the remainder of the software is
likely to contain, the effects of the wrapper are negligible. As such, worrying
about the wrapper is likely to be a micro-optimization, unless dealing with
call stack limitations. The wrapper in these tests simply invokes the wrapped
method with \code{Function.apply()}, forwarding all arguments.}
\label{fig:func-wrap-perf}
\end{figure*}
Many readers are likely to be concerned about a decision that wraps every
function of our definition object, as this will require two function calls each
time a method is invoked. \fref{fig:func-wrap-perf-invoke} shows why this detail
is likely to be a concern --- invoking our wrapped function is so slow in
comparison to invoking the original function directly that the solution seems
prohibitive. However, one must consider how functions are \emph{actually} used
--- to perform some sort of business logic. It is rare that we would invoke
bodiless functions continuously in a loop. Rather, we should take into
consideration the \emph{percent change between function invocations that contain
some sort of business logic}. This is precisely what
\frefpg{fig:func-wrap-perf-blogic} takes into consideration, showing that our
invocation worry is would actually be a micro-optimization. For example, in
software that performs DOM manipulation, the performance impact of
wrapper invocation is likely to be negligible due to repaints being highly
intensive operations.
One legitimate concern of our wrapper implementation, however, is limited
call stack space. The wrapper will effectively cut the remaining stack space in
half if dealing with recursive operations on itself, which may be a problem
for environments that do not support tail call optimizations, or for algorithms
that are not written in such a way that tail call optimizations can be
performed.\footnote{Another concern is that the engine may not be able to
perform tail call optimization because the function may recurse on the wrapper
instead of itself.} In such a situation, we can avoid the problem entirely by
recommending that heavy recursive algorithms do not invoke wrapped methods;
instead, the recursive operation can be performed using ``normal'' (unwrapped)
functions and its result returned by a wrapped method call.
\begin{figure}[t]
\center
\input{data/stack-limits.tex}
\caption{Call stack limits of various common browsers. \cite{oreilly-hpj}
Determining the call stack limit for your own environment is as simple as
incrementing a counter for each recursive call until an error is thrown.}
\label{fig:stack-limits}
\end{figure}
That said, call stack sizes for ECMAScript environments are growing increasingly
larger. Call stack limits for common browsers (including historical versions for
comparison) are listed in \frefpg{fig:stack-limits}. Should this limit be
reached, another alternative is to use \func{setTimeout()} to reset the stack
and continue the recursive operation. This can also have the benefit of making
the operation asynchronous.
Factoring this logic into the constructor factory is further complicated by our
inability to distinguish between members intended to be public and those
intended to be private. In section~\ref{sec:encap}, this issue was not a concern
because the members could be explicitly specified separately per implementation.
With the factory, we are provided only a single definition object; asking for
multiple would be confusing, messy and unnatural to those coming from other
classical object-oriented languages. Therefore, our second task shall be to
augment \func{copyTo()} in \jsref{lst:ctor-factory} to distinguish between
public and private members.
Section~\ref{sec:privileged} mentioned the convention of using a single
underscore as a prefix for member names to denote a private member (e.g.
\code{this.\_foo}). We will adopt this convention for our definition object, as
it is both simple and performant (only a single-character check). Combining this
concept with the wrapper implementation, we arrive at
\jsref{lst:ctor-factory-priv}.
\lstinputlisting[%
label=lst:ctor-factory-priv,
caption=Altering the constructor factory in \jsref{lst:ctor-factory} to
support private methods in a manner similar to \jsref{lst:method-priv},
lastline=97
]{lst/ctor-factory-priv.js}
(INCOMPLETE.)