2011-03-23 20:52:25 -04:00
|
|
|
@c This document is part of the ease.js manual
|
|
|
|
@c Copyright (c) 2011 Mike Gerwitz
|
|
|
|
@c Permission is granted to copy, distribute and/or modify this document
|
|
|
|
@c under the terms of the GNU Free Documentation License, Version 1.3
|
|
|
|
@c or any later version published by the Free Software Foundation;
|
|
|
|
@c with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
|
|
|
|
@c Texts. A copy of the license is included in the section entitled ``GNU
|
|
|
|
@c Free Documentation License''.
|
|
|
|
|
|
|
|
@node Implementation Details
|
|
|
|
@appendix Implementation Details / Rationale
|
|
|
|
The majority of the development time spent on ease.js was not hacking away at
|
|
|
|
the source code. Rather, it was spent with pen and paper. Every aspect of
|
|
|
|
ease.js was heavily planned from the start. Every detail was important to ensure
|
|
|
|
a consistent implementation that worked, was fast and that developers would
|
|
|
|
enjoy working with. Failures upfront or alterations to the design in later
|
|
|
|
versions would break backwards compatibility unnecessarily and damage the
|
|
|
|
reputation of the project.
|
|
|
|
|
|
|
|
When using ease.js, developers may wonder why things were implemented in the
|
|
|
|
manner that they were. Perhaps they have a problem with the implementation, or
|
|
|
|
just want to learn how the project works. This project was an excellent learning
|
|
|
|
experience that deals very closely with the power and flexibility of prototypal
|
|
|
|
programming. In an attempt to appease both parties, this appendix is provided to
|
|
|
|
provide some details and rationale behind ease.js.
|
|
|
|
|
|
|
|
@menu
|
|
|
|
* Class Module Design::
|
|
|
|
@end menu
|
|
|
|
|
|
|
|
|
|
|
|
@node Class Module Design
|
|
|
|
@section Class Module Design
|
|
|
|
The @var{Class} module, which is accessible via @samp{require( 'easejs'
|
|
|
|
).Class}, is the backbone of the entire project. In a class-based
|
|
|
|
Object-Oriented model, as one could guess by the name, the class is the star
|
|
|
|
player. When the project began, this was the only initial implementation detail.
|
|
|
|
Everything else was later layered atop of it.
|
|
|
|
|
|
|
|
As such, developing the Class module took the most thought and presented the
|
|
|
|
largest challenge throughout the project. Every detail of its implementation
|
|
|
|
exists for a reason. Nothing was put in place because the author simply ``felt
|
|
|
|
like it''. The project aims to exist as a strong, reliable standard for the
|
|
|
|
development of JavaScript-based applications. If such a goal is to be attained,
|
|
|
|
the feature set and implementation details would have to be strongly functional,
|
|
|
|
easy to use and make sense to the Object-Oriented developer community.
|
|
|
|
|
|
|
|
The design also requires a strong understanding of Object-Oriented development.
|
|
|
|
Attention was paid to the nuances that could otherwise introduce bugs or an
|
|
|
|
inconsistent implementation.
|
|
|
|
|
|
|
|
@menu
|
|
|
|
* Class Declaration Syntax::
|
|
|
|
* Class Storage::
|
|
|
|
* Constructor Implementation::
|
2011-04-12 23:19:54 -04:00
|
|
|
* Static Implementation::
|
2011-03-23 20:52:25 -04:00
|
|
|
@end menu
|
|
|
|
|
|
|
|
@node Class Declaration Syntax
|
|
|
|
@subsection Class Declaration Syntax
|
|
|
|
Much thought was put into how a class should be declared. The chosen style
|
|
|
|
serves as syntatic sugar, making the declarations appear very similar to classes
|
|
|
|
in other Object-Oriented languages.
|
|
|
|
|
|
|
|
The original style was based on John Resig's blog post about a basic means of
|
|
|
|
extending class-like objects (@pxref{About}). That style was
|
|
|
|
@samp{Class.extend()} to declare a new class and @samp{Foo.extend()} to extend
|
|
|
|
an existing class. This implementation is still supported for creating anonymous
|
|
|
|
classes. However, a means needed to be provided to create named classes. In
|
|
|
|
addition, invoking @code{extend()} on an empty class seemed unnecessary.
|
|
|
|
|
|
|
|
The next incarnation made the @var{Class} module invokable. Anonymous classes
|
|
|
|
could be defined using @samp{Class( @{@} )} and named classes could be defined
|
|
|
|
by passing in a string as the first argument: @samp{Class( 'Foo', @{@} )}.
|
|
|
|
Classes could still be extended using the previously mentioned syntax, but that
|
|
|
|
did no justice if we need to provide a class name. Therefore, the @samp{Class(
|
|
|
|
'SubFoo' ).extend( Supertype, @{@} )} syntax was also adopted.
|
|
|
|
|
|
|
|
JavaScript's use of curly braces to represent objects provides a very convenient
|
|
|
|
means of making class definitions look like actual class definitions. By
|
|
|
|
convention, the opening brace for the declaration object is on its own line, to
|
|
|
|
make it look like an opening block.
|
|
|
|
|
|
|
|
@float Figure, f:class-def-syntax
|
|
|
|
@verbatim
|
|
|
|
Class( 'Foo' )
|
|
|
|
.implement( Bar )
|
|
|
|
.extend(
|
|
|
|
{
|
|
|
|
'public foo': function()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Syntax and style of class definition}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Syntax for implementing interfaces and extending classes was another
|
|
|
|
consideration. The implementation shown above was chosen for a couple of
|
|
|
|
reasons. Firstly, verbs were chosen in order to (a) prevent the use of reserved
|
|
|
|
words and (b) to represent that the process was taking place at @emph{runtime},
|
|
|
|
@emph{as} the code was being executed. Unlike a language like C++ or Java, the
|
|
|
|
classes are not prepared at compile-time.
|
|
|
|
|
|
|
|
@node Class Storage
|
|
|
|
@subsection Class Storage
|
|
|
|
One of the more powerful features of ease.js is how classes (and other objects,
|
|
|
|
such as Interfaces) are stored. Rather than adopting its own model, the decision
|
|
|
|
was instead to blend into how JavaScript already structures its data. Everything
|
|
|
|
in JavaScript can be assigned to a variable, including functions. Classes are no
|
|
|
|
different.
|
|
|
|
|
|
|
|
One decision was whether or not to store classes internally by name, then permit
|
|
|
|
accessing it globally (wherever ease.js is available). This is how most
|
|
|
|
Object-Oriented languages work. If the file in which the class is defined is
|
|
|
|
available, the class can generally be referenced by name. This may seem natural
|
|
|
|
to developers coming from other Object-Oriented languages. The decision was to
|
|
|
|
@emph{not} adopt this model.
|
|
|
|
|
|
|
|
By storing classes @emph{only} in variables, we have fine control over the
|
|
|
|
scope and permit the developer to adopt their own mechanism for organizing their
|
|
|
|
classes. For example, if the developer wishes to use namespacing, then he/she is
|
|
|
|
free to assign the class to a namespace (e.g. @samp{org.foo.my.ns.Foo =
|
|
|
|
Class( @{@} )}). More importantly, we can take advantage of the CommonJS format
|
|
|
|
that ease.js was initially built for by assigning the class to
|
|
|
|
@code{module.exports}. This permits @samp{require( 'filename' )} to return the
|
|
|
|
class.
|
|
|
|
|
|
|
|
This method also permits defining anonymous classes (while not necessarily
|
|
|
|
recommended, they have their uses just as anonymous functions do), mimic the
|
|
|
|
concept of Java's inner classes and create temporary classes (@pxref{Temporary
|
|
|
|
Classes}). Indeed, we can do whatever scoping that JavaScript permits.
|
|
|
|
|
|
|
|
@subsubsection Memory Management
|
|
|
|
Memory management is perhaps one of the most important considerations.
|
|
|
|
Initially, ease.js encapsulated class metadata and visibility structures.
|
|
|
|
However, it quickly became apparent that this method of storing data, although
|
|
|
|
excellent for protecting data from being manipulated, caused what appeared to be
|
|
|
|
memory leaks in long-running software. These were in fact not memory leaks, but
|
|
|
|
ease.js was keeping references to class data with no idea when to free them.
|
|
|
|
|
|
|
|
If the globally accessible model would have been adopted (storing classes
|
|
|
|
internally by class name rather than in variables), classes would not have been
|
|
|
|
freed from memory when they went out of scope. This raises the memory footprint
|
|
|
|
unnecessarily, especially for temporary classes. It would make sense that, after
|
|
|
|
a temporary class is done being used, that the class be freed from memory.
|
|
|
|
|
|
|
|
Given this fact alone, the author firmly believes that the model that was chosen
|
|
|
|
was the best choice.
|
|
|
|
|
|
|
|
@node Constructor Implementation
|
|
|
|
@subsection Constructor Implementation
|
|
|
|
ease.js uses a PHP-style constructor. Rather than using the class name as the
|
|
|
|
constructor, a @code{__construct()} method is used. This was chosen primarily
|
|
|
|
because ease.js does not always know the name of the class. In fact, in the
|
|
|
|
early stages of development, named classes were unsupported. With the PHP-style
|
|
|
|
constructor, the class name does not need to be known, allowing constructors to
|
|
|
|
be written for anonymous and named classes alike.
|
|
|
|
|
|
|
|
In addition, the PHP-style constructor is consistent between class definitions.
|
|
|
|
To look up a constructor, one need only search for ``__construct'', rather than
|
|
|
|
the class name. This makes certain operations, such as global searching (using
|
|
|
|
@command{grep} or any other utility), much simpler.
|
|
|
|
|
2011-03-23 21:35:25 -04:00
|
|
|
One difference from PHP is the means of preventing instantiation. In PHP, if the
|
|
|
|
constructor is declared as non-public, then an error will be raised when the
|
|
|
|
developer attempts to instantiate the class. ease.js did not go this route, as
|
|
|
|
the method seems cryptic. Instead, an exception should be thrown in the
|
|
|
|
constructor if the developer doesn't wish the class to be instantiated. In the
|
|
|
|
future, a common method may be added for consistency/convenience.
|
|
|
|
|
2011-03-23 20:52:25 -04:00
|
|
|
The constructor is optional. If one is not provided, nothing is done after the
|
|
|
|
class is instantiated (aside from the internal ease.js initialization tasks).
|
|
|
|
|
|
|
|
The constructor is called after all initialization tasks have been completed.
|
|
|
|
|
2011-04-12 23:19:54 -04:00
|
|
|
@node Static Implementation
|
|
|
|
@subsection Static Implementation
|
|
|
|
The decisions behind ease.js's static implementation were very difficult. More
|
|
|
|
thought and time was spent on paper designing how the static implementation
|
|
|
|
should be represented than most other features in the project. The reason for
|
|
|
|
this is not because the concept of static members is complicated. Rather, it is
|
|
|
|
due to limitations of pre-ECMAScript 5 engines.
|
|
|
|
|
|
|
|
@subsubsection How Static Members Are Supposed To Work
|
|
|
|
The first insight into the problems a static implementation would present was
|
|
|
|
the concept itself. Take any common Object-Oriented language such as C++, Java,
|
|
|
|
or even PHP. Static members are inherited by subtypes @emph{by reference}. What
|
|
|
|
does this mean? Consider two classes: @var{Foo} and @var{SubFoo}, the latter of
|
|
|
|
which inherits from the former. @var{Foo} defines a static property @var{count}
|
|
|
|
to be incremented each time the class is instantiated. The subtype @var{SubFoo},
|
|
|
|
when instantiated (assuming the constructor is not overridden), would increment
|
|
|
|
that very same count. Therefore, we can represent this by static that
|
|
|
|
@samp{Foo.count === SubFoo.count}. In the example below, we demonstrate this
|
|
|
|
concept in pseudocode:
|
|
|
|
|
|
|
|
@float Figure, f:static-ref-pseudocode
|
|
|
|
@verbatim
|
|
|
|
let Foo = Class
|
|
|
|
public static count = 0
|
|
|
|
let SubFoo extend from Foo
|
|
|
|
|
|
|
|
Foo.count = 5
|
|
|
|
SubFoo.count === 5 // true
|
|
|
|
SubFoo.count = 6
|
|
|
|
Foo.count === 6 // true
|
|
|
|
@end verbatim
|
|
|
|
@caption{Representing static properties in pseudocode}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
As you may imagine, this is a problem. The above example does not look very
|
|
|
|
JS-like. That is because it isn't. JS does not provide a means for variables to
|
|
|
|
share references to the same primitive. In fact, even Objects are passed by
|
|
|
|
value in the sense that, if the variable is reassigned, the other variable
|
|
|
|
remains unaffected. The concept we are looking to support is similar to a
|
|
|
|
pointer in C/C++, or a reference in PHP.
|
|
|
|
|
|
|
|
We have no such luxury.
|
|
|
|
|
|
|
|
@subsubsection Emulating References
|
|
|
|
Fortunately, ECMAScript 5 provides a means to @emph{emulate} references --
|
|
|
|
getters and setters. Taking a look at @ref{f:static-ref-pseudocode}, we can
|
|
|
|
clearly see that @var{Foo} and @var{SubFoo} are completely separate objects.
|
|
|
|
They do not share any values by references. We shouldn't share primitives by
|
|
|
|
reference even if we wanted to. This issue can be resolved by using
|
|
|
|
getters/setters on @var{SubFoo} and @emph{forwarding} gets/sets to the
|
|
|
|
supertype:
|
|
|
|
|
|
|
|
@float Figure, f:static-ref-forward
|
|
|
|
@verbatim
|
|
|
|
var obj1 = { val: 1 },
|
|
|
|
obj2 = {
|
|
|
|
get val()
|
|
|
|
{
|
|
|
|
return obj1.val;
|
|
|
|
},
|
|
|
|
|
|
|
|
set val( value )
|
|
|
|
{
|
|
|
|
obj1.val = value;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
obj2.val; // 1
|
|
|
|
obj2.val = 5;
|
|
|
|
obj1.val; // 5
|
|
|
|
|
|
|
|
obj1.val = 6;
|
|
|
|
obj2.val // 6
|
|
|
|
@end verbatim
|
|
|
|
@caption{Emulating references with getters/setters (proxy)}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
This comes with considerable overhead when compared to accessing the properties
|
|
|
|
directly (in fact, at the time of writing this, V8 doesn't even attempt to
|
|
|
|
optimize calls to getters/setters, so it is even slower than invoking accessor
|
|
|
|
methods). That point aside, it works well and accomplishes what we need it to.
|
|
|
|
|
|
|
|
There's just one problem. @emph{This does not work in pre-ES5 environments!}
|
|
|
|
ease.js needs to support older environments, falling back to ensure that
|
|
|
|
everything operates the same (even though features such as visibility aren't
|
|
|
|
present).
|
|
|
|
|
|
|
|
This means that we cannot use this proxy implementation. It is used for
|
|
|
|
visibility in class instances, but that is because a fallback is possible. It is
|
|
|
|
not possible to provide a fallback that works with two separate objects. If
|
|
|
|
there were, we wouldn't have this problem in the first place.
|
|
|
|
|
|
|
|
@subsubsection Deciding On a Compromise
|
|
|
|
A number of options were available regarding how static properties should be
|
|
|
|
implemented. Methods are not a problem -- they are only accessed by reference,
|
|
|
|
never written to. Therefore, they can keep their convenient @samp{Foo.method()}
|
|
|
|
syntax. Unfortunately, that cannot be the case for properties without the
|
|
|
|
ability to implement a proxy through the use of getters/setters (which, as
|
|
|
|
aforementioned, requires the services of ECMAScript 5, which is not available in
|
|
|
|
older environments).
|
|
|
|
|
|
|
|
The choices were has follows:
|
|
|
|
|
|
|
|
@enumerate
|
|
|
|
@item
|
|
|
|
Add another object to be shared between classes (e.g. @samp{Foo.$}).
|
|
|
|
|
|
|
|
@item
|
|
|
|
Do not inherit by reference. Each subtype would have their own distinct value.
|
|
|
|
|
|
|
|
@item
|
|
|
|
Access properties via an accessor method (e.g. @samp{Foo.$('var')}), allowing us
|
|
|
|
to properly proxy much like a getter/setter.
|
|
|
|
@end enumerate
|
|
|
|
|
|
|
|
There are problems with all of the above options. The first option, which
|
|
|
|
involves sharing an object, would cause awkward inheritance in the case of a
|
|
|
|
fallback. Subtypes would set their static properties on the object, which would
|
|
|
|
make that property available to the @emph{supertype}! That is tolerable in the
|
|
|
|
case of a fallback. However, the real problem lies in two other concepts: when a
|
|
|
|
class has two subtypes that attempt to define a property with the same name, or
|
|
|
|
when a subtype attempts to override a property. The former would cause both
|
|
|
|
subtypes (which are entirely separate from one-another, with the exception of
|
|
|
|
sharing the same parent) to share the same values, which is unacceptable. The
|
|
|
|
latter case can be circumvented by simply preventing overriding of static
|
|
|
|
properties, but the former just blows this idea out of the water entirely.
|
|
|
|
|
|
|
|
The second option is to @emph{not} inherit by reference. This was the initial
|
|
|
|
implementation (due to JavaScript limitations) until it was realized that this
|
|
|
|
caused far too many inconsistencies between other Object-Oriented languages.
|
|
|
|
There is no use in introducing a different implementation when we are attempting
|
|
|
|
to mirror classic Object-Oriented principals to present a familiar paradigm to
|
|
|
|
developers. Given this inconsistency alone, this option simply will not work.
|
|
|
|
|
|
|
|
The final option is to provide an accessor method, much like the style of
|
|
|
|
jQuery. This would serve as an ugly alternative for getters/setters. It would
|
|
|
|
operate as follows:
|
|
|
|
|
|
|
|
@float Figure, f:static-accessor-impl
|
|
|
|
@verbatim
|
|
|
|
// external
|
|
|
|
Foo.$('var'); // getter
|
|
|
|
Foo.$( 'var, 'foo' ); // setter
|
|
|
|
|
|
|
|
// internal
|
|
|
|
this.__self.$('var'); // getter
|
|
|
|
this.__self.$( 'var', 'foo' ); // setter
|
|
|
|
@end verbatim
|
|
|
|
@caption{Accessor implementation for static properties}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Obviously, this is highly inconsistent with the rest of the framework, which
|
|
|
|
permits accessing properties in the conventional manner. However, this
|
|
|
|
implementation does provide a number key benefits:
|
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
It provides an implementation that is @emph{consistent with other
|
|
|
|
Object-Oriented languages}. This is the most important point!
|
|
|
|
|
|
|
|
@item
|
|
|
|
The accessor method parameter style is common in other frameworks like jQuery.
|
|
|
|
|
|
|
|
@item
|
|
|
|
The method name (@var{$}) is commonly used to denote a variable in scripting
|
|
|
|
languages (such as PHP and shells, or to denote a scalar in Perl).
|
|
|
|
|
|
|
|
@item
|
|
|
|
It works consistently in ES5 and pre-ES5 environments alike.
|
|
|
|
@end itemize
|
|
|
|
|
|
|
|
So, although the syntax is inconsistent with the rest of the framework, it does
|
|
|
|
address all of our key requirements. This makes it a viable option for our
|
|
|
|
implementation.
|
|
|
|
|
|
|
|
@subsubsection Appeasing ES5-Only Developers
|
|
|
|
There is another argument to be had. ease.js is designed to operate across all
|
|
|
|
major browsers for all major versions, no matter how ridiculous (e.g. Internet
|
|
|
|
Explorer 5.5), so long as it does not require unreasonable development effort.
|
|
|
|
That is great and all, but what about those developers who are developing
|
|
|
|
@emph{only} for an ECMAScript 5 environment? This includes developers leveraging
|
|
|
|
modern HTML 5 features and those using Node.js who do not intend to share code
|
|
|
|
with pre-ES5 clients. Why should they suffer from an ugly, unnecessary syntax
|
|
|
|
when a beautiful, natural [and elegant] implementation is available using
|
|
|
|
proxies via getters/setters?
|
|
|
|
|
|
|
|
There are certainly two sides to this argument. On one hand, it is perfectly
|
|
|
|
acceptable to request a natural syntax if it is supported. On the other hand,
|
|
|
|
this introduces a number of problems:
|
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
This may make libraries written using ease.js unportable (to older
|
|
|
|
environments). If written using an ES5-only syntax, they would have no way to
|
|
|
|
fall back for static properties.
|
|
|
|
|
|
|
|
@item
|
|
|
|
The syntax differences could be very confusing, especially to those beginning to
|
|
|
|
learn ease.js. They may not clearly understand the differences, or may go to use
|
|
|
|
a library in their own code, and find that things do not work as intended.
|
|
|
|
Code examples would also have to make clear note of what static syntax they
|
|
|
|
decided to use. It adds a layer of complexity.
|
|
|
|
@end itemize
|
|
|
|
|
|
|
|
Now, those arguing for the cleaner syntax can also argue that all newer
|
|
|
|
environments moving forward will support the clean, ES5-only syntax, therefore
|
|
|
|
it would be beneficial to have. Especially when used for web applications that
|
|
|
|
can fall back to an entirely different implementation or refuse service entirely
|
|
|
|
to older browsers. Why hold ease.js back for those stragglers if there's no
|
|
|
|
intent on ever supporting them?
|
|
|
|
|
|
|
|
Both arguments are solid. Ultimately, ease.js will likely favor the argument of
|
|
|
|
implementing the cleaner syntax by providing a runtime flag. If enabled, static
|
|
|
|
members will be set using proxies. If not, it will fall back to the uglier
|
|
|
|
implementation using the accessor method. If the environment doesn't support the
|
|
|
|
flag when set, ease.js will throw an error and refuse to run, or will invoke a
|
|
|
|
fallback specified by the developer to run an alternative code base that uses
|
|
|
|
the portable, pre-ES5 syntax.
|
|
|
|
|
|
|
|
This decision will ultimately be made in the future. For the time being, ease.js
|
|
|
|
will support and encourage use of the portable static property syntax.
|
|
|
|
|