959 lines
42 KiB
TeX
959 lines
42 KiB
TeX
\section{Hacking Around Prototypal Limitations}
|
|
\label{sec:hack-around}
|
|
Section~\ref{sec:class-like} demonstrated how one would work within the
|
|
limitations of conventional ECMAScript to produce class-like objects using
|
|
prototypes. For those coming from other classical object-oriented languages,
|
|
these features are insufficient. In order to address many of the remaining
|
|
issues, more elaborate solutions are necessary.
|
|
|
|
It should be noted that all the hacks in this section will, in some way or
|
|
another, introduce additional overhead, although it should be minimal in
|
|
comparison with the remainder of the software that may implement them.
|
|
Performance considerations will be mentioned where the author finds it to be
|
|
appropriate. Do not let this concern deter you from using these solutions in
|
|
your own code --- always benchmark to determine where the bottleneck lies in
|
|
your software.
|
|
|
|
\subsection{Extensible Constructors: Revisited}
|
|
\label{sec:extending}
|
|
Section~\ref{sec:ext-ctor} discussed improving constructor design to allow for
|
|
extensibility and to improve performance. However, the solution presented did
|
|
not provide a consistent means of creating extensible constructors with, for
|
|
example, optional argument lists.
|
|
|
|
The only way to ensure that the constructor will bypass validation and
|
|
initialization logic only when used as a prototype is to somehow indicate that
|
|
it is being used as such. Since prototype assignment is in no way different than
|
|
any other assignment, no distinction can be made. As such, we must create our
|
|
own.
|
|
|
|
\begin{lstlisting}[%
|
|
label=lst:ctor-extend,
|
|
caption=Working around prototype extending issues
|
|
]
|
|
var Foo = ( function( extending )
|
|
{
|
|
var F = function( name )
|
|
{
|
|
if ( extending ) return;
|
|
|
|
if ( typeof name !== 'string' )
|
|
{
|
|
throw TypeError( "Invalid name" );
|
|
}
|
|
|
|
this.name = name || "Default";
|
|
|
|
// hypothetical; impl. left to reader
|
|
this.hash = createHash();
|
|
};
|
|
|
|
F.asPrototype = function()
|
|
{
|
|
extending = true;
|
|
|
|
var proto = new F();
|
|
|
|
extending = false;
|
|
return proto;
|
|
};
|
|
|
|
F.prototype = {
|
|
// getName(), etc...
|
|
};
|
|
|
|
return F;
|
|
} )( false );
|
|
|
|
function SubFoo() { /* ... */ }
|
|
SubFoo.prototype = Foo.asPrototype(); // OK
|
|
// ...
|
|
|
|
var foo1 = new Foo();
|
|
foo1.getName(); // "Default"
|
|
foo1.hash; // "..."
|
|
|
|
var foo2 = new Foo( "Bar" );
|
|
foo2.getName(); // "Bar"
|
|
foo2.hash; // "..."
|
|
\end{lstlisting}
|
|
|
|
One solution, as demonstrated in \jsref{lst:ctor-extend}, is to use a variable
|
|
(e.g. \var{extending}) to indicate to a constructor when it is being used to
|
|
extend a prototype. The constructor, acting as a closure, can then check the
|
|
value of this flag to determine whether or not to immediately return, avoiding
|
|
all construction logic. This implementation would allow us to return only a
|
|
prototype, which is precisely what we are looking for.
|
|
|
|
It is unlikely that we would want to expose \var{extending} directly for
|
|
modification, as this would involve manually setting the flag before requesting
|
|
the prototype, then remembering to reset it after we are done. Should the user
|
|
forget to reset the flag, all future calls to the constructor would continue to
|
|
ignore all constructor logic, which could lead to confusing bugs in the
|
|
software. To work around this issue, \jsref{lst:ctor-extend} offers an
|
|
\func{asPrototype()} method on \var{Foo} itself, which will set the flag, create
|
|
a new instance of \var{Foo}, reset the flag and return the new
|
|
instance.\footnote{In classical terms, \func{asPrototype()} can be thought of as
|
|
a static factory method of \var{Foo}.}
|
|
|
|
In order to cleanly encapsulate our extension logic, \var{Foo} is generated
|
|
within a self-executing function (using much the same concept as privileged
|
|
members in section~\ref{sec:privileged}, with a slightly different
|
|
application).\footnote{Self-executing functions are most often used to introduce
|
|
scope, allowing for the encapsulation of certain data. In this case, we
|
|
encapsulate our extension logic and return our constructor (assigned to \var{F}
|
|
within the self-executing function), which is then assigned to \var{Foo}. Note
|
|
the parenthesis immediately following the anonymous function, which invokes it
|
|
with a single argument to give \var{extending} a default value of \code{false}.
|
|
This pattern of encapsulation and exporting specific values is commonly referred
|
|
to as the \dfn{Module Pattern}.} This gives \var{Foo} complete control over when
|
|
its constructor logic should be ignored. Of course, one would not want to
|
|
duplicate this mess of code for each and every constructor they create.
|
|
Factoring this logic into a common, re-usable implementation will be discussed a
|
|
bit later as part of a class system (see section~\ref{sec:ctor-factory}).
|
|
|
|
\subsection{Encapsulating Data}
|
|
\label{sec:encap}
|
|
We discussed a basic means of encapsulation with privileged members in
|
|
section~\ref{sec:privileged}. Unfortunately, the solution, as demonstrated in
|
|
\jsref{lst:privileged}, involves redeclaring methods that could have otherwise
|
|
been defined within the prototype and shared between all instances. With that
|
|
goal in mind, let us consider how we may be able to share data for multiple
|
|
instances with a single method definition in the prototype.
|
|
|
|
We already know from \jsref{lst:ctor-extend} that we can truly encapsulate data
|
|
for a prototype within a self-executing function. Methods can then, acting as
|
|
closures, access that data that is otherwise inaccessible to the remainder of
|
|
the software. With that example, we concerned ourselves with only a single piece
|
|
of data --- the \var{extending} flag. This data has no regard for individual
|
|
instances (one could think of it as static data, in classical terms). Using
|
|
\jsref{lst:ctor-extend} as a starting point, we can build a system that will
|
|
keep track of data \emph{per-instance}. This data will be accessible to all
|
|
prototype members.
|
|
|
|
\subsubsection{A Naive Implementation}
|
|
\label{sec:encap-naive}
|
|
One approach to our problem involves to assigning each instance a unique
|
|
identifier (an ``instance id'', or \var{iid}). For our implementation, this
|
|
identifier will simply be defined as an integer that is incremented each time
|
|
the constructor is invoked.\footnote{There is, of course, a maximum
|
|
number of instances with this implementation. Once \var{iid} reaches
|
|
\var{Number.MAX\_NUMBER}, its next assignment will cause it to overflow to
|
|
\code{Number.POSITIVE\_INFINITY}. This number, however, can be rather large. On
|
|
one 64-bit system under v8, \code{Number.MAX\_NUMBER =
|
|
1.7976931348623157e+308.}} This instance id could be used as a key for a data
|
|
variable that stores data for each instance. Upon instantiation, the instance
|
|
id could be assigned to the new instance as a property (we'll worry about
|
|
methods of ``encapsulating'' this property later).
|
|
|
|
\begin{lstlisting}[%
|
|
label=lst:encap-naive,
|
|
caption=Encapsulating data with shared members (a naive implementation)
|
|
]
|
|
var Stack = ( function()
|
|
{
|
|
var idata = [],
|
|
iid = 0;
|
|
|
|
var S = function()
|
|
{
|
|
// assign a unique instance identifier
|
|
// to each instance
|
|
this.__iid = iid++;
|
|
|
|
idata[ this.__iid ] = {
|
|
stack: []
|
|
};
|
|
};
|
|
|
|
S.prototype = {
|
|
push: function( val )
|
|
{
|
|
idata[ this.__iid ]
|
|
.stack.push( val );
|
|
},
|
|
|
|
pop: function()
|
|
{
|
|
return idata[ this.__iid ]
|
|
.stack.pop();
|
|
}
|
|
};
|
|
|
|
return S;
|
|
} )();
|
|
|
|
var first = new Stack(),
|
|
second = new Stack();
|
|
|
|
first.push( "foo" );
|
|
second.push( "bar" );
|
|
|
|
first.pop(); // "foo"
|
|
second.pop(); // "bar"
|
|
\end{lstlisting}
|
|
|
|
\jsref{lst:encap-naive} demonstrates a possible stack implementation using the
|
|
principals that have just been described. Just like \jsref{lst:ctor-extend}, a
|
|
self-executing function is used to encapsulate our data and returns the
|
|
\var{Stack} constructor.\footnote{The reader should take note that we have
|
|
omitted our extensible constructor solution discussed in
|
|
section~\ref{sec:extending} for the sake of brevity.} In addition to the
|
|
instance id, the instance data is stored in the array \var{idata} (an array is
|
|
appropriate here since \var{iid} is sequential and numeric). \var{idata} will
|
|
store an object for each instance, each acting in place of \keyword{this}. Upon
|
|
instantiation, the private properties for the new instance are initialized using
|
|
the newly assigned instance id.
|
|
|
|
Because \var{idata} is not encapsulated within the constructor, we do not need
|
|
to use the concept of privileged members (see section~\ref{sec:privileged}); we
|
|
need only define the methods in such a way that \var{idata} is still within
|
|
scope. Fortunately, this allows us to define the methods on the prototype,
|
|
saving us method redeclarations with each call to the constructor, improving
|
|
overall performance.
|
|
|
|
This implementation comes at the expense of brevity and creates a diversion from
|
|
common ECMAScript convention when accessing data for a particular instance using
|
|
prototypes. Rather than having ECMAScript handle this lookup process for us, we
|
|
must do so manually. The only data stored on the instance itself (bound to
|
|
\keyword{this}) is the instance id, \var{iid}, which is used to look up the
|
|
actual members from \var{idata}. Indeed, this is the first concern --- this is a
|
|
considerable amount of boilerplate code to create separately for each prototype
|
|
wishing to encapsulate data in this manner.
|
|
|
|
An astute reader may raise concern over our \var{\_\_iid} assignment on each
|
|
instance. Firstly, although this name clearly states ``do not touch'' with its
|
|
double-underscore prefix,\footnote{Certain languages used double-underscore to
|
|
indicate something internal to the language or system. This also ensures the
|
|
name will not conflict with any private members that use the single-underscore
|
|
prefix convention.} the member is still public and enumerable.\footnote{The term
|
|
\dfn{enumerable} simply means that it can be returned by \keyword{foreach}.}
|
|
There is no reason why we should be advertising this internal data to the world.
|
|
Secondly, imagine what may happen if a user decides to alter the value of
|
|
\var{\_\_iid} for a given instance. Although such a modification would create
|
|
some fascinating (or even ``cool'') features, it could also wreak havoc on a
|
|
system and break encapsulation.\footnote{Consider that we know a stack is
|
|
encapsulated within another object. We could exploit this \var{\_\_iid}
|
|
vulnerability to gain access to the data of that encapsulated object as follows,
|
|
guessing or otherwise calculating the proper instance id: \code{( new Stack()
|
|
).\_\_iid = iid\_of\_encapsulated\_stack\_instance}.}
|
|
|
|
In environments supporting ECMAScript 5 and later, we can make the property
|
|
non-enumerable and read-only using \code{Object.defineProperty()} in place of
|
|
the \var{\_\_iid} assignment:
|
|
|
|
\begin{verbatim}
|
|
Object.defineProperty( this, '__iid', {
|
|
value: iid++,
|
|
|
|
writable: false,
|
|
enumerable: false,
|
|
configurable: false
|
|
} );
|
|
\end{verbatim}
|
|
|
|
The \var{configurable} property simply determines whether or not we can
|
|
re-configure a property in the future using \code{Object.defineProperty()}. It
|
|
should also be noted that each of the properties, with the exception of
|
|
\var{value}, default to \code{false}, so they may be omitted; they were included
|
|
here for clarity.
|
|
|
|
Of course, this solution leaves a couple loose ends: it will work only on
|
|
ECMAScript 5 and later environments (that have support for
|
|
\code{Object.defineProperty()}) and it still does not prevent someone from
|
|
spying on the instance id should they know the name of the property
|
|
(\var{\_\_iid}) ahead of time. However, we do need the instance id to be a
|
|
member of the instance itself for our lookup process to work properly.
|
|
|
|
At this point, many developers would draw the line and call the solution
|
|
satisfactory. An internal id, although unencapsulated, provides little room for
|
|
exploitation.\footnote{You could get a total count of the number of instances of
|
|
a particular prototype, but not much else.} For the sake of discussion and the
|
|
development of a more concrete implementation, let us consider a potential
|
|
workaround for this issue.
|
|
|
|
For pre-ES5\footnote{Hereinafter, ECMAScript 5 and ES5 will be used
|
|
interchangably.} environments, there will be no concrete solution, since all
|
|
properties will always be enumerable. However, we can make it more difficult by
|
|
randomizing the name of the \var{\_\_iid} property, which would require that the
|
|
user filter out all known properties or guess at the name. In ES5+ environments,
|
|
this would effectively eliminate the problem entirely,\footnote{Of course,
|
|
debuggers are always an option. There is also the possibility of exploiting
|
|
weaknesses in a random name implementation; one can never completely eliminate
|
|
the issue.} since the property name cannot be discovered or be known beforehand.
|
|
Consider, then, the addition of another variable within the self-executing
|
|
function --- \var{iid\_name} --- which we could set to some random value (the
|
|
implementation of which we will leave to the reader). Then, when initializing or
|
|
accessing values, one would use the syntax:
|
|
|
|
\begin{verbatim}
|
|
idata[ this[ iid_name ] ].stack // ...
|
|
\end{verbatim}
|
|
|
|
Of course, this introduces additional overhead, although it is likely to be
|
|
negligible in comparison with the rest of the software.
|
|
|
|
With that, we have contrived a solution to our encapsulation problem.
|
|
Unfortunately, as the title of this section indicates, this implementation is
|
|
naive to a very important consideration --- memory consumption. The problem is
|
|
indeed so severe that this solution cannot possibly be recommended in practice,
|
|
although the core concepts have been an excellent experiment in ingenuity and
|
|
have provided a strong foundation on which to expand.\footnote{It is my hope
|
|
that the title of this section will help to encourage those readers that simply
|
|
skim for code to continue reading and consider the flaws of the design rather
|
|
than adopting them.}
|
|
|
|
\subsubsection{A Proper Implementation}
|
|
\label{sec:encap-proper}
|
|
Section~\ref{sec:encap-naive} proposed an implementation that would permit the
|
|
true encapsulation of instance data, addressing the performance issues
|
|
demonstrated in \jsref{lst:privileged}. Unfortunately, the solution offered in
|
|
\jsref{lst:encap-naive} is prone to terrible memory leaks. In order to
|
|
understand why, we must first understand, on a very basic level, how garbage
|
|
collection (GC) is commonly implemented in environments that support ECMAScript.
|
|
|
|
\dfn{Garbage collection} refers to an automatic cleaning of data (and
|
|
subsequent freeing of memory, details of which vary between implementations)
|
|
that is no longer ``used''. Rather than languages like C that require manual
|
|
allocation and freeing of memory, the various engines that implement ECMAScript
|
|
handle this process for us, allowing the developer to focus on the task at hand
|
|
rather than developing a memory management system for each piece of software.
|
|
Garbage collection can be a wonderful thing in most circumstances, but one must
|
|
understand how it recognizes data that is no longer being ``used'' in order to
|
|
ensure that the memory is properly freed. If data lingers in memory in such a
|
|
way that the software will not access it again and that the garbage collector is
|
|
not aware that the data can be freed, this is referred to as a \dfn{memory
|
|
leak}.\footnote{The term ``memory leak'' implies different details depending on
|
|
context --- in this case, it varies between languages. A memory leak in C is
|
|
handled much differently than a memory leak in ECMAScript environments. Indeed,
|
|
memory leaks in systems with garbage collectors could also be caused by bugs in
|
|
the GC process itself, although this is not the case here.}
|
|
|
|
One method employed by garbage collectors is reference counting; when an object
|
|
is initially created, the reference count is set to one. When a reference to
|
|
that object is stored in another variable, that count is incremented by one.
|
|
When a variable containing a reference to a particular object falls out of
|
|
scope, is deleted, or has the value reassigned, the reference count is
|
|
decremented by one. Once the reference count reaches zero, it is scheduled for
|
|
garbage collection.\footnote{What happens after this point is
|
|
implementation-defined.} The concept is simple, but is complicated by the use of
|
|
closures. When an object is referenced within a closure, or even has the
|
|
\emph{potential} to be referenced through another object, it cannot be garbage
|
|
collected.
|
|
|
|
In the case of \jsref{lst:encap-naive}, consider \var{idata}. With each new
|
|
instance, \var{iid} is incremented and an associated entry added to \var{idata}.
|
|
The problem is --- ECMAScript does not have destructor support. Since we cannot
|
|
tell when our object is GC'd, we cannot free the \var{idata} entry. Because each
|
|
and every object within \var{idata} has the \emph{potential} to be referenced at
|
|
some point in the future, even though our implementation does not allow for it,
|
|
it cannot be garbage collected. The reference count for each index of
|
|
\var{idata} will forever be $\geq 1$.
|
|
|
|
To resolve this issue without altering this implementation, there is only one
|
|
solution --- to offer a method to call to manually mark the object as destroyed.
|
|
This defeats the purpose of garbage collection and is an unacceptable solution.
|
|
Therefore, our naive implementation contains a fatal design flaw. This extends
|
|
naturally into another question --- how do we work with garbage collection to
|
|
automatically free the data for us?
|
|
|
|
The answer to this question is already known from nearly all of our prior
|
|
prototype examples. Unfortunately, it is an answer that we have been attempting
|
|
to work around in order to enforce encapsulation --- storing the data on the
|
|
instance itself. By doing so, the data is automatically freed (if the reference
|
|
count is zero, of course) when the instance itself is freed. Indeed, we have hit
|
|
a wall due to our inability to explicitly tell the garbage collector when the
|
|
data should be freed.\footnote{There may be an implementation out there
|
|
somewhere that does allow this, or a library that can interface with the garbage
|
|
collector. However, it would not be portable.} The solution is to find a common
|
|
ground between \jsref{lst:privileged} and \jsref{lst:encap-naive}.
|
|
|
|
Recall our original goal --- to shy away from the negative performance impact of
|
|
privileged members without exposing each of our private members as public. Our
|
|
discussion has already revealed that we are forced to store our data on the
|
|
instance itself to ensure that it will be properly freed by the garbage
|
|
collector once the reference count reaches zero. Recall that
|
|
section~\ref{sec:encap-naive} provided us with a number of means of making our
|
|
only public member, \var{\_\_iid}, considerably more difficult to access, even
|
|
though it was not fully encapsulated. This same concept can be applied to our
|
|
instance data.
|
|
|
|
\begin{lstlisting}[%
|
|
label=lst:encap-inst,
|
|
caption=Encapsulating data on the instance itself (see also
|
|
\jsref{lst:encap-naive})
|
|
]
|
|
var Stack = ( function()
|
|
{
|
|
// implementation left to reader
|
|
var _privname = genRandomName();
|
|
|
|
var S = function()
|
|
{
|
|
Object.defineProperty( this, _privname, {
|
|
enumerable: false,
|
|
writable: false,
|
|
configurable: false,
|
|
|
|
value: {
|
|
stack: []
|
|
}
|
|
} );
|
|
};
|
|
|
|
S.prototype = {
|
|
push: function( val )
|
|
{
|
|
this[ _privname ].stack.push( val );
|
|
},
|
|
|
|
pop: function()
|
|
{
|
|
return this[ _privname ].stack.pop();
|
|
}
|
|
};
|
|
|
|
return S;
|
|
} )();
|
|
\end{lstlisting}
|
|
|
|
\jsref{lst:encap-inst} uses a random, non-enumerable property to make the
|
|
discovery of the private data considerably more difficult.\footnote{The property
|
|
is also read-only, but that does not necessarily aid encapsulation. It prevents
|
|
the object itself from being reassigned, but not any of its members.} The random
|
|
name, \var{\_privname}, is used in each of the prototypes to look up the data on
|
|
the appropriate instance (e.g. \code{this[ \_privname ].stack} in place of
|
|
\code{this.stack}).\footnote{One may decide that the random name is unnecessary
|
|
overhead. However, note that static names would permit looking up the data if
|
|
the name is known ahead of time.} This has the same effect as
|
|
\jsref{lst:encap-naive}, with the exception that it is a bit easier to follow
|
|
without the instance management code and that it does not suffer from memory
|
|
leaks due to GC issues.
|
|
|
|
Of course, this implementation depends on features introduced in ECMAScript 5
|
|
--- namely, \code{Object.defineProperty()}, as introduced in
|
|
section~\ref{sec:encap-naive}. In order to support pre-ES5 environments, we
|
|
could define our own fallback \func{defineProperty()} method by directly
|
|
altering \var{Object},\footnote{The only circumstance I ever recommend modifying
|
|
built-in bojects/prototypes is to aid in backward compatibility; it is otherwise
|
|
a very poor practice that creates tightly coupled, unportable code.} as
|
|
demonstrated in \jsref{lst:defprop}.
|
|
|
|
\begin{lstlisting}[%
|
|
label=lst:defprop,
|
|
caption=A fallback \code{Object.defineProperty()} implementation
|
|
]
|
|
Object.defineProperty = Object.defineProperty
|
|
|| function( obj, name, config )
|
|
{
|
|
obj[ name ] = config.value;
|
|
};
|
|
\end{lstlisting}
|
|
|
|
Unfortunately, a fallback implementation is not quite so simple. Certain
|
|
dialects may only partially implement \code{Object.createProperty()}. In
|
|
particular, I am referring to Internet Explorer 8's incomplete
|
|
implementation.\footnote{IE8's dialect is JScript.} Surprisingly, IE8 only
|
|
supports this action on DOM elements, not all objects. This puts us in a
|
|
terribly awkward situation --- the method is defined, but the implementation is
|
|
``broken''. As such, our simple and fairly concise solution in
|
|
\jsref{lst:defprop} is insufficient. Instead, we need to perform a more
|
|
complicated check to ensure that not only is the method defined, but also
|
|
functional for our particular uses. This check is demonstrated in
|
|
\jsref{lst:defprop-check}, resulting in a boolean value which can be used to
|
|
determine whether or not the fallback in \jsref{lst:defprop} is necessary.
|
|
|
|
\begin{lstlisting}[%
|
|
label=lst:defprop-check,
|
|
caption=Working around IE8's incomplete \code{Object.defineProperty()}
|
|
implementation (taken from ease.js)
|
|
]
|
|
var can_define_prop = ( function()
|
|
{
|
|
try
|
|
{
|
|
Object.defineProperty( {}, 'x', {} );
|
|
}
|
|
catch ( e ) { return false; }
|
|
|
|
return true;
|
|
} )();
|
|
\end{lstlisting}
|
|
|
|
This function performs two checks simultaneously --- it first checks to see if
|
|
\code{Object.defineProperty()} exists and then ensures that we are not using
|
|
IE8's broken implementation. If the invocation fails, that will mean that the
|
|
method does not exist (or is not properly defined), throwing an exception which
|
|
will immediately return false. If attempting to define a property using this
|
|
method on a non-DOM object in IE8, an exception will also be thrown, returning
|
|
false. Therefore, we can simply attempt to define a property on an empty object.
|
|
If this action succeeds, then \code{Object.defineProperty()} is assumed to be
|
|
sufficiently supported. The entire process is enclosed in a self-executing
|
|
function to ensure that the check is performed only once, rather than a function
|
|
that performs the check each time it is called. The merriment of this result to
|
|
\jsref{lst:defprop} is trivial and is left to the reader.
|
|
|
|
It is clear from this fallback, however, that our property is enumerable in
|
|
pre-ES5 environments. At this point, a random property name would not be all
|
|
that helpful and the reader may decide to avoid the random implementation in its
|
|
entirety.
|
|
|
|
\subsubsection{Private Methods}
|
|
\label{sec:priv-methods}
|
|
Thus far, we have been dealing almost exclusively with the issue of
|
|
encapsulating properties. Let us now shift our focus to the encapsulation of
|
|
other private members, namely methods (although this could just as easily be
|
|
applied to getters/setters in ES5+ environments). Private methods are actually
|
|
considerably easier to conceptualize, because the data does not vary between
|
|
instances --- a method is a method and is shared between all instances. As such,
|
|
we do not have to worry about the memory management issues addressed in
|
|
section~\ref{sec:encap-proper}.
|
|
|
|
Encapsulating private members would simply imply moving the members outside of
|
|
the public prototype (that is, \code{Stack.prototype}). One would conventionally
|
|
implement private methods using privileged members (as in
|
|
section~\ref{sec:privileged}), but it is certainly pointless redefining the
|
|
methods for each instance, since \jsref{lst:encap-inst} provided us with a means
|
|
of accessing private data from within the public prototype. Since the
|
|
self-executing function introduces scope for our private data (instead of the
|
|
constructor), we do not need to redefine the methods for each new instance.
|
|
Instead, we can create what can be considered a second, private prototype.
|
|
|
|
\begin{lstlisting}[%
|
|
label=lst:method-priv,
|
|
caption=Implementing shared private methods without privileged members
|
|
]
|
|
var Stack = ( function()
|
|
{
|
|
var _privname = getRandomName();
|
|
|
|
var S = function()
|
|
{
|
|
// ... (see previous examples)
|
|
};
|
|
|
|
var priv_methods = {
|
|
getStack: function()
|
|
{
|
|
return this[ _privname ].stack;
|
|
}
|
|
};
|
|
|
|
S.prototype = {
|
|
push: function( val )
|
|
{
|
|
var stack = priv_methods.getStack
|
|
.call( this );
|
|
stack.push( val );
|
|
},
|
|
|
|
pop: function()
|
|
{
|
|
var stack = priv_methods.getStack
|
|
.call( this )
|
|
return stack.pop( val );
|
|
}
|
|
};
|
|
|
|
return S;
|
|
} )();
|
|
\end{lstlisting}
|
|
|
|
\jsref{lst:method-priv} illustrates this concept of a private
|
|
prototype.\footnote{Alternatively, to reduce code at the expense of clarity, one
|
|
could simply define functions within the closure to act as private methods
|
|
without assigning them to \var{priv\_methods}. Note that \func{call()} is still
|
|
necessary in that situation.} The object \var{priv\_methods} acts as a second
|
|
prototype containing all members that are private and shared between all
|
|
instances, much like the conventional prototype. \code{Stack.prototype} then
|
|
includes only the members that are intended to be public. In this case, we have
|
|
defined a single private method --- \func{getStack()}.
|
|
|
|
Recall how \keyword{this} is bound automatically for prototype methods (see
|
|
section~\ref{sec:proto}). ECMAScript is able to do this for us because of the
|
|
standardized \var{prototype} property. For our private methods, we have no such
|
|
luxury. Therefore, we are required to bind \keyword{this} to the proper object
|
|
ourselves through the use of \code{Function.call()} (or, alternatively,
|
|
\code{Function.apply()}). The first argument passed to \func{call()} is the
|
|
object to which \keyword{this} will be bound, which we will refer to as the
|
|
\dfn{context}. This, unfortunately, increases the verbosity of private method
|
|
calls, but successfully provides us with a private prototype implementation.
|
|
|
|
Since private members needn't be inherited by subtypes, no additional work needs
|
|
to be done.
|
|
|
|
\subsection{Protected Members}
|
|
\label{sec:prot}
|
|
We have thus far covered two of the three access modifiers (see
|
|
section~\ref{sec:encap}) --- public and private. Those implementations allowed
|
|
us to remain blissfully ignorant of inheritance, as public members are handled
|
|
automatically by ECMAScript and private members are never inherited by subtypes.
|
|
The concept of protected members is a bit more of an interesting study since it
|
|
requires that we put thought into providing subtypes with access to encapsulated
|
|
data, \emph{without} exposing this data to the rest of the world.
|
|
|
|
From an implementation perspective, we can think of protected members much like
|
|
private; they cannot be part of the public prototype, so they must exist in
|
|
their own protected prototype and protected instance object. The only difference
|
|
here is that we need a way to expose this data to subtypes. This is an issue
|
|
complicated by our random name implementation (see
|
|
section~\ref{sec:encap-proper}); without it, subtypes would be able to access
|
|
protected members of its parent simply by accessing a standardized property
|
|
name. The problem with that is --- if subtypes can do it, so can other,
|
|
completely unrelated objects. As such, we will focus on a solution that works in
|
|
conjunction with our randomized name (an implementation with a standardized
|
|
name is trivial).
|
|
|
|
In order for the data to remain encapsulated, the name must too remain
|
|
encapsulated. This means that the subtype cannot request the name from the
|
|
parent; instead, we must either have access to the random name or we must
|
|
\emph{tell} the parent what the name should be. The latter will not work
|
|
per-instance with the implementation described in
|
|
section~\ref{sec:encap-proper}, as the methods are not redefined per-instance
|
|
and therefore must share a common name. Let us therefore first consider the
|
|
simpler of options --- sharing a common protected name between the two classes.
|
|
|
|
\begin{lstlisting}[%
|
|
label=lst:prot-share,
|
|
caption=Sharing protected members with subtypes
|
|
]
|
|
var _protname = getRandomName();
|
|
|
|
var Stack = ( function()
|
|
{
|
|
var _privname = getRandomName();
|
|
|
|
var S = function()
|
|
{
|
|
// ... (see previous examples)
|
|
|
|
Object.defineProperty( this, _privname, {
|
|
value: { stack: [] }
|
|
} );
|
|
|
|
Object.defineProperty( this, _protname, {
|
|
value: { empty: false }
|
|
} );
|
|
};
|
|
|
|
// a means of sharing protected methods
|
|
Object.defineProperty( S, _protname, {
|
|
getStack: function()
|
|
{
|
|
return this[ _privname ].stack;
|
|
}
|
|
} );
|
|
|
|
S.prototype = {
|
|
push: function( val )
|
|
{
|
|
var stack = S[ _protname ].getStack
|
|
.call( this );
|
|
stack.push( val );
|
|
|
|
this[ _protname ].empty = false;
|
|
},
|
|
|
|
pop: function()
|
|
{
|
|
var stack = this[ _protname ]
|
|
.getStack.call( this )
|
|
|
|
this[ _protname ].empty =
|
|
( stack.length === 0 );
|
|
|
|
return stack.pop( val );
|
|
}
|
|
};
|
|
|
|
S.asPrototype = function()
|
|
{
|
|
// ... (see previous examples)
|
|
};
|
|
|
|
return S;
|
|
} )();
|
|
|
|
|
|
var MaxStack = ( function()
|
|
{
|
|
var M = function( max )
|
|
{
|
|
// call parent constructor
|
|
Stack.call( this );
|
|
|
|
// we could add to our protected members
|
|
// (in practice, this would be private, not
|
|
// protected)
|
|
this[ _protname ].max = +max;
|
|
};
|
|
|
|
// override push
|
|
M.prototype.push = function( val )
|
|
{
|
|
var stack = Stack[ _protname ].getStack
|
|
.call( this );
|
|
|
|
if ( stack.length ===
|
|
this[ _protname ].max
|
|
)
|
|
{
|
|
throw Error( "Maximum reached." );
|
|
};
|
|
|
|
// call parent method
|
|
Stack.prototype.push.call( this, val );
|
|
};
|
|
|
|
// add a new method demonstrating parent
|
|
// protected property access
|
|
M.prototype.isEmpty = function()
|
|
{
|
|
return this[ _protname ].empty;
|
|
};
|
|
|
|
M.prototype = Stack.asPrototype();
|
|
M.prototype.constructor = M;
|
|
|
|
return M;
|
|
} )();
|
|
|
|
|
|
var max = new MaxStack( 2 );
|
|
max.push( "foo" );
|
|
max.push( "bar" );
|
|
max.push( "baz" ); // Error
|
|
max.pop(); // "bar"
|
|
max.pop(); // "foo"
|
|
\end{lstlisting}
|
|
|
|
\jsref{lst:prot-share} makes an attempt to demonstrate a protected property and
|
|
method implementation while still maintaining the distinction between it and the
|
|
private member implementation (see section~\ref{sec:encap-proper}). The example
|
|
contains two separate constructors --- \var{Stack} and \var{MaxStack}, the
|
|
latter of which extends \var{Stack} to limit the number of items that may be
|
|
pushed to it. \var{Stack} has been modified to include a protected property
|
|
\var{empty}, which will be set to \code{true} when the stack contains no items,
|
|
and a protected method \var{getStack()}, which both \var{Stack} and its subtype
|
|
\var{MaxStack} may use to access the private property \var{stack} of
|
|
\var{Stack}.
|
|
|
|
The key part of this implementation is the declaration of \var{\_protname}
|
|
within the scope of both types (\var{Stack} and \var{MaxStack}).\footnote{One
|
|
would be wise to enclose all of \jsref{lst:prot-share} within a function to
|
|
prevent \var{\_protname} from being used elsewhere, exporting \var{Stack} and
|
|
\var{MaxStack} however the reader decides.} This declaration allows both
|
|
prototypes to access the protected properties just as we would the private
|
|
data. Note that \var{\_privname} is still defined individually within each type,
|
|
as this data is unique to each.
|
|
|
|
Protected methods, however, need additional consideration. Private methods, when
|
|
defined within the self-executing function that returns the constructor, work
|
|
fine when called from within the associated prototype (see
|
|
section~\ref{sec:priv-methods}). However, since they're completely encapsulated,
|
|
we cannot use the same concept for protected methods --- the subtype would not
|
|
have access to the methods. Our two options are to either declare the protected
|
|
members outside of the self-executing function (as we do \var{\_privname}), which
|
|
makes little organizational sense, or to define the protected members on the
|
|
constructor itself using \var{\_protname} and
|
|
\code{Object.defineProperty()}\footnote{See section~\ref{sec:encap-proper} for
|
|
\code{Object.defineProperty()} workarounds/considerations.} to encapsulate it
|
|
the best we can. We can then use the shared \var{\_protname} to access the
|
|
methods on \var{Stack}, unknown to the rest of the world.
|
|
|
|
An astute reader may realize that \jsref{lst:prot-share} does not permit the
|
|
addition of protected methods without also modifying the protected methods of
|
|
the supertype and all other subtypes; this is the same reason we assign new
|
|
instances of constructors to the \var{prototype} property. Additionally,
|
|
accessing a protected method further requires referencing the same constructor
|
|
on which it was defined. Fixing this implementation is left as an exercise to
|
|
the reader.
|
|
|
|
Of course, there is another glaring problem with this implementation --- what
|
|
happens if we wish to extend one of our prototypes, but are not within the scope
|
|
of \var{\_protname} (which would be the case if you are using
|
|
\jsref{lst:prot-share} as a library, for example)? With this implementation,
|
|
that is not possible. As such, \jsref{lst:prot-share} is not recommended unless
|
|
you intended to have your prototypes act like final classes.\footnote{A
|
|
\dfn{final} class cannot be extended.} As this will not always be the case, we
|
|
must put additional thought into the development of a solution that allows
|
|
extending class-like objects with protected members outside of the scope of the
|
|
protected name \var{\_protname}.
|
|
|
|
As we already discussed, we cannot request the protected member name from the
|
|
parent, as that will provide a means to exploit the implementation and gain
|
|
access to the protected members, thereby breaking encapsulation. Another
|
|
aforementioned option was \emph{telling} the parent what protected member name
|
|
to use, perhaps through the use of \func{asPrototype()} (see
|
|
section~\ref{sec:extending}). This is an option for protected \emph{properties},
|
|
as they are initialized with each new instance, however it is not a clean
|
|
implementation for \emph{members}, as they have already been defined on the
|
|
constructor with the existing \var{\_protname}. Passing an alternative name
|
|
would result in something akin to:
|
|
|
|
\begin{verbatim}
|
|
Object.defineProperty( S, _newname, {
|
|
value: S[ _protname ]
|
|
} );
|
|
\end{verbatim}
|
|
|
|
This would quickly accumulate many separate protected member references on the
|
|
constructor --- one for each subtype. As such, this implementation is also left
|
|
as an exercise for an interested reader; we will not explore it
|
|
further.\footnote{The reader is encouraged to attempt this implementation to
|
|
gain a better understanding of the concept. However, the author cannot recommend
|
|
its use in a production environment.}
|
|
|
|
The second option is to avoid exposing protected property names entirely. This
|
|
can be done by defining a function that can expose the protected method object.
|
|
This method would use a system-wide protected member name to determine what
|
|
objects to return, but would never expose the name --- only the object
|
|
references. However, this does little to help us with our protected properties,
|
|
as a reference to that object cannot be returned until instantiation. As such,
|
|
one could use a partial implementation of the previously suggested
|
|
implementation in which one provides the protected member name to the parent(s).
|
|
Since the protected members would be returned, the duplicate reference issue
|
|
will be averted.
|
|
|
|
The simplest means of demonstrating this concept is to define a function that
|
|
accepts a callback to be invoked with the protected method object. A more
|
|
elegant implementation will be described in future sections, so a full
|
|
implementation is also left as an exercise to the reader. \jsref{lst:prot-func}
|
|
illustrates a skeleton implementation.\footnote{Should the reader decide to take
|
|
up this exercise, keep in mind that the implementation should also work with
|
|
multiple supertypes (that is, type3 extends type2 extends type1).} The
|
|
\func{def} function accepts the aforementioned callback with an optional first
|
|
argument --- \var{base} --- from which to retrieve the protected methods.
|
|
|
|
\begin{lstlisting}[%
|
|
label=lst:prot-func,
|
|
caption=Exposing protected methods with a callback (brief illustration; full
|
|
implementation left as an exercise for the reader)
|
|
]
|
|
var def = ( function()
|
|
{
|
|
var _protname = getRandomName();
|
|
|
|
return function( base, callback )
|
|
{
|
|
var args = Array.prototype.slice.call(
|
|
arguments
|
|
),
|
|
|
|
callback = args.pop(),
|
|
base = args.pop() || {};
|
|
|
|
return callback( base[ _protname ] );
|
|
};
|
|
} )();
|
|
|
|
var Stack = def( function( protm )
|
|
{
|
|
// ...
|
|
|
|
return S;
|
|
} );
|
|
|
|
var MaxStack = def( Stack, function( protm )
|
|
{
|
|
// for properties only
|
|
var _protname = getRandomName();
|
|
|
|
// ...
|
|
|
|
// asPrototype() would accept the protected
|
|
// member name
|
|
M.protoype = S.asPrototype( _protname );
|
|
M.prototype.constructor = M;
|
|
|
|
return M;
|
|
} );
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Protected Member Encapsulation Challenges}
|
|
Unfortunately, the aforementioned implementations do not change a simple fact
|
|
--- protected members are open to exploitation, unless the prototype containing
|
|
them cannot be extended outside of the library/implementation. Specifically,
|
|
there is nothing to prevent a user from extending the prototype and defining a
|
|
property or method to return the encapsulated members.
|
|
|
|
Consider the implementation described in \jsref{lst:prot-func}. We could define
|
|
another subtype, \var{ExploitedStack}, as shown in \jsref{lst:prot-exploit}.
|
|
This malicious type exploits our implementation by defining two methods ---
|
|
\func{getProtectedProps()} and \func{getProtectedMethods()} --- that return
|
|
the otherwise encapsulated data.
|
|
|
|
\begin{lstlisting}%
|
|
[label=lst:prot-exploit,
|
|
caption=Exploiting \jsref{lst:prot-func} by returning protected members.
|
|
]
|
|
var ExploitedStack = def( Stack, function( protm )
|
|
{
|
|
var _protname = getRandomName();
|
|
|
|
var E = function() { /* ... */ };
|
|
|
|
E.prototype.getProtectedProps = function()
|
|
{
|
|
return this[ _protname ];
|
|
}:
|
|
|
|
E.prototype.getProtectedMethods = function()
|
|
{
|
|
return protm;
|
|
};
|
|
|
|
E.prototype = Stack.asPrototype( _protname );
|
|
E.prototype.constructor = E;
|
|
|
|
return E;
|
|
} )();
|
|
\end{lstlisting}
|
|
|
|
Fortunately, our random \var{\_protname} implementation will only permit
|
|
returning data for the protected members of that particular instance. Had we not
|
|
used random names, there is a chance that an object could be passed to
|
|
\func{getProtectedProps()} and have its protected properties
|
|
returned.\footnote{Details depend on implementation. If a global protected
|
|
property name is used, this is trivial. Otherwise, it could be circumstantial
|
|
--- a matching name would have to be guessed, known, or happen by chance.} As
|
|
such, this property exploit is minimal and would only hurt that particular
|
|
instance. There could be an issue if supertypes contain sensitive protected
|
|
data, but this is an implementation issue (sensitive data should instead be
|
|
private).
|
|
|
|
Methods, however, are a more considerable issue. Since the object exposed via
|
|
\func{def()} is \emph{shared} between each of the instances, much like its
|
|
parent prototype is, it can be used to exploit each and every instance (even if
|
|
the reader has amended \jsref{lst:prot-share} to resolve the aforementioned
|
|
protected member addition bug, since \code{Object.getPrototypeOf()} can be
|
|
used to work around this amendment). Someone could, for example, reassign
|
|
\code{Stack[ \_protname ].getStack()} to do something else;
|
|
\var{Object.defineProperty()} in \jsref{lst:prot-share} only made \code{Stack[
|
|
\_protname ]} \emph{itself} read-only. The object itself, however, can be
|
|
modified. This can be amended by using \code{Object.defineProperty()} for each
|
|
and every protected method, which is highly verbose and cumbersome.
|
|
|
|
Once we rule out the ability to modify protected method definitions,\footnote{Of
|
|
course, this doesn't secure the members in pre-ES5 environments.} we still must
|
|
deal with the issue of having our protected methods exposed and callable. For
|
|
example, one could do the following to gain access to the private \var{stack}
|
|
object:
|
|
|
|
\begin{verbatim}
|
|
( new ExploitedStack() ).getProtectedMethods()
|
|
.getStack.call( some_stack_instance );
|
|
\end{verbatim}
|
|
|
|
Unfortunately, there is little we can do about this type of exploit besides
|
|
either binding\footnote{See \func{Function.bind()}.} each method call (which
|
|
would introduce additional overhead per instance) or entirely preventing the
|
|
extension of our prototypes outside of our own library/software. By creating a
|
|
protected API, you are exposing certain aspects of your prototype to the rest of
|
|
the world; this likely breaks encapsulation and, in itself, is often considered
|
|
a poor practice.\footnote{\code{Stack.getStack()} breaks encapsulation because
|
|
it exposes the private member \var{stack} to subtypes.} An alternative is to
|
|
avoid inheritance altogether and instead favor composition, thereby evading this
|
|
issue entirely. That is a pretty attractive concept, considering how verbose and
|
|
messy this protected hack has been.
|