@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:: * Static Implementation:: @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. 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. 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. @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.