1226 lines
44 KiB
Plaintext
1226 lines
44 KiB
Plaintext
@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 Classes
|
|
@chapter Working With Classes
|
|
In Object-Oriented programming, the most common term you are likely to encounter
|
|
is ``Class''. A @dfn{class} is like a blueprint for creating an @dfn{object},
|
|
which is an @dfn{instance} of that class. Classes contain @dfn{members}, which
|
|
include primarily properties and methods. A @dfn{property} is a value, much like
|
|
a variable, that a class ``owns''. A @dfn{method}, when comparing with
|
|
JavaScript, is a function that is ``owned'' by a class. As a consequence,
|
|
properties and methods are not part of the global scope.
|
|
|
|
JavaScript does not support classes in the manner traditionally understood by
|
|
Object-Oriented programmers. This is because JavaScript follows a different
|
|
model which instead uses prototypes. Using this model, JavaScript supports
|
|
basic instantiation and inheritance. Rather than instantiating classes,
|
|
JavaScript instantiates constructors, which are functions. The following example
|
|
illustrates how you would typically create a class-like object in JavaScript:
|
|
|
|
@float Figure, f:class-js
|
|
@verbatim
|
|
/**
|
|
* Declaring "classes" WITHOUT ease.js
|
|
*/
|
|
|
|
// our "class"
|
|
var MyClass = function()
|
|
{
|
|
this.prop = 'foobar';
|
|
}
|
|
|
|
// a class method
|
|
MyClass.prototype.getProp = function()
|
|
{
|
|
return this.prop;
|
|
};
|
|
|
|
// create a new instance of the class and execute doStuff()
|
|
var foo = new MyClass();
|
|
console.log( foo.getProp() ); // outputs "foobar"
|
|
@end verbatim
|
|
@caption{Basic ``Class'' in JavaScript @emph{without} using ease.js}
|
|
@end float
|
|
|
|
This gets the job done, but the prototypal paradigm has a number of limitations
|
|
amongst its incredible flexibility. For Object-Oriented programmers, it's both
|
|
alien and inadequate. That is not to say that it is not useful. In fact, it is
|
|
so flexible that an entire Object-Oriented framework was able to be built atop
|
|
of it.
|
|
|
|
ease.js aims to address the limitations of the prototype model and provide a
|
|
familiar environment for Object-Oriented developers. Developers should not have
|
|
to worry about @emph{how} classes are implemented in JavaScript (indeed, those
|
|
details should be encapsulated). You, as a developer, should be concerned with
|
|
only how to declare and use the classes. If you do not understand what a
|
|
prototype is, that should be perfectly fine. You shouldn't need to understand it
|
|
in order to use the library (though, it's always good to understand what a
|
|
prototype is when working with JavaScript).
|
|
|
|
In this chapter and those that follow, we will see the limitations that ease.js
|
|
addresses. We will also see how to declare the classes using both prototypes and
|
|
ease.js, until such a point where prototypes are no longer adequate.
|
|
|
|
@menu
|
|
* Defining Classes:: Learn how to define a class with ease.js
|
|
* Inheritance:: Extending classes from another
|
|
* Static Members:: Members whose use do not require instantiation
|
|
@end menu
|
|
|
|
|
|
@node Defining Classes
|
|
@section Defining Classes
|
|
@table @code
|
|
@item C = Class( string @var{name}, Object @var{dfn} )
|
|
Define named class @var{C} identified by @var{name} described by @var{dfn}.
|
|
|
|
@item C = Class( string @var{name} ).extend( Object @var{dfn} )
|
|
Define named class @var{C} identified by @var{name} described by @var{dfn}.
|
|
|
|
@item C = Class( Object @var{dfn} )
|
|
Define anonymous class @var{C} as described by @var{dfn}.
|
|
|
|
@item C = Class.extend( Object @var{dfn } )
|
|
Define anonymous class @var{C} as described by @var{dfn}.
|
|
@end table
|
|
|
|
Class @var{C} can be defined in a number of manners, as listed above, provided
|
|
a @dfn{definition object} @var{dfn} containing the class members and options. An
|
|
optional string @var{name} may be provided to set an internal identifier for
|
|
@var{C}, which may be used for reflection and error messages. If @var{name} is
|
|
omitted, @var{C} will be declared anonymous.
|
|
|
|
@code{Class} must be imported (@pxref{Including}) from @code{easejs.Class}; it
|
|
is not available in the global scope.
|
|
|
|
@anchor{dfnobj}
|
|
@subsection Definition Object
|
|
@table @code
|
|
@item dfn = @{ '[@var{keywords}] @var{name}': @var{value}[, ...] @}
|
|
Define definition object @var{dfn} containing a member identified by @var{name},
|
|
described by optional @var{keywords} with the value of @var{value}. The member
|
|
type is determined by @code{typeof} @var{value}. Multiple members may be
|
|
provided in a single definition object.
|
|
@end table
|
|
|
|
The definition object @var{dfn} has the following properties:
|
|
@enumerate
|
|
@item
|
|
The keys represent the @dfn{member declaration}, which may optionally contain
|
|
one or more @var{keywords} delimited by spaces. A space must delimit the final
|
|
keyword and @var{name}.
|
|
@enumerate
|
|
@item
|
|
@var{keywords} must consist only of recognized tokens, delimited by spaces.
|
|
|
|
@item
|
|
Each token in @var{keywords} must be unique per @var{name}.
|
|
@end enumerate
|
|
|
|
@item
|
|
The @var{value} represents the @dfn{member definition}, the type of which
|
|
determines what type of member will be declared.
|
|
@enumerate
|
|
@item
|
|
A @var{value} of type @code{function} will define a @dfn{method}, which is an
|
|
invokable member whose context is assigned to the class or class instance
|
|
depending on @var{keywords}.
|
|
|
|
@item
|
|
All other types of @var{value} will define a @dfn{property} - a mutable
|
|
value equal to @var{value}, assigned to a class or instance depending on
|
|
@var{keywords}. Properties may be made immutable using @var{keywords}.
|
|
|
|
@item
|
|
Getters/setters may be defined in an ECMAScript 5 or greater environment.
|
|
Getters/setters must share the same value for @var{keywords}.
|
|
@end enumerate
|
|
|
|
@item
|
|
@var{name} must be unique across all members of @var{dfn}.
|
|
@end enumerate
|
|
|
|
@subsection Member Validations
|
|
For any member @var{name}:
|
|
@itemize
|
|
@item
|
|
@var{keywords} of member @var{name} may contain only one access modifier
|
|
(@pxref{Access Modifiers}).
|
|
@item
|
|
See @ref{Member Keywords,,Member Keywords} for @var{keywords} restrictions.
|
|
@end itemize
|
|
|
|
For any member @var{name} declared as a @emph{method}, the following must hold
|
|
true:
|
|
@itemize
|
|
@item
|
|
@var{keywords} of member @var{name} may not contain
|
|
@ref{Member Keywords,,@code{override}} without a super method of the same
|
|
@var{name} (@pxref{Inheritance}).
|
|
@item
|
|
@var{keywords} of member @var{name} may contain both
|
|
@ref{Member Keywords,,@code{static}} and @ref{Member Keywords,,@code{virtual}}
|
|
keywords (@pxref{Static Members} and @ref{Inheritance}).
|
|
@item
|
|
@var{keywords} of member @var{name} may not contain the
|
|
@ref{Member Keywords,,@code{const}} keyword.
|
|
@end itemize
|
|
|
|
@subsection Discussion
|
|
In @ref{f:class-js}, we saw how one would conventionally declare a class-like
|
|
object (a prototype) in JavaScript. This method is preferred for many
|
|
developers, but it is important to recognize that there is a distinct difference
|
|
between Prototypal and Classical Object-Oriented development models. Prototypes
|
|
lack many of the conveniences and features that are provided by Classical
|
|
languages, but they can be emulated with prototypes. As an Object-Oriented
|
|
developer, you shouldn't concern yourself with @emph{how} a class is declared in
|
|
JavaScript. In true OO fashion, that behavior should be encapsulated. With
|
|
ease.js, it is.
|
|
|
|
Let's take a look at how to declare that exact same class using ease.js:
|
|
|
|
@float Figure, f:class-easejs
|
|
@verbatim
|
|
var Class = require( 'easejs' ).Class;
|
|
|
|
var MyClass = Class(
|
|
{
|
|
'public prop': 'foobar',
|
|
|
|
'public getProp': function()
|
|
{
|
|
return this.prop;
|
|
}
|
|
} );
|
|
|
|
// create a new instance of the class and execute doStuff()
|
|
var foo = MyClass();
|
|
console.log( foo.getProp() ); // outputs "foobar"
|
|
@end verbatim
|
|
@caption{Basic anonymous class declaration using ease.js}
|
|
@end float
|
|
|
|
That should look much more familiar to Object-Oriented developers. There are a
|
|
couple important notes before we continue evaluating this example:
|
|
|
|
@itemize
|
|
@item
|
|
The first thing you will likely notice is our use of the @code{public} keyword.
|
|
This is optional (the default visibility is public), but always recommended.
|
|
Future versions of ease.js may provide warnings when the visibility is omitted.
|
|
We will get more into visibility later on.
|
|
|
|
@item
|
|
Unlike @ref{f:class-js,}, we do not use the @code{new} keyword in order to
|
|
instantiate our class. You are more than welcome to use the @code{new} keyword
|
|
if you wish, but it is optional when using ease.js. This is mainly because
|
|
without this feature, if the keyword is omitted, the constructor is called as a
|
|
normal function, which could have highly negative consequences. This style of
|
|
instantiation also has its benefits, which will be discussed later on.
|
|
|
|
@item
|
|
ease.js's class module is imported using @code{require()} in the above example.
|
|
If using ease.js client-side (@pxref{Client-Side Include}), you can instead use
|
|
@samp{var Class = easejs.Class}. From this point on, importing the module will
|
|
not be included in examples.
|
|
@end itemize
|
|
|
|
The above example declares an anonymous class, which is stored in the
|
|
variable @var{MyClass}. By convention, we use CamelCase, with the first letter
|
|
capital, for class names (and nothing else).
|
|
|
|
@menu
|
|
* Anonymous vs. Named Classes::
|
|
* Constructors:: How to declare a constructor
|
|
* Temporary Classes:: Throwaway classes that only need to be used once
|
|
* Temporary Instances:: Throwaway instances that only need to be used once
|
|
@end menu
|
|
|
|
@node Anonymous vs. Named Classes
|
|
@subsection Anonymous vs. Named Classes
|
|
We state that @ref{f:class-easejs,} declared an @dfn{anyonmous class} because
|
|
the class was not given a name. Rather, it was simply assigned to a variable,
|
|
which itself has a name. To help keep this idea straight, consider the common
|
|
act of creating anonymous functions in JavaScript:
|
|
|
|
@float Figure, f:anon-func
|
|
@verbatim
|
|
// anonymous
|
|
var myFunc = function() {};
|
|
|
|
// named
|
|
function myNamedFunc() {};
|
|
@end verbatim
|
|
@caption{Anonymous functions in JavaScript}
|
|
@end float
|
|
|
|
If the function itself is not given a name, it is considered to be anonymous,
|
|
even though it is stored within a variable. Just as the engine has no idea what
|
|
that function is named, ease.js has no idea what the class is named because it
|
|
does not have access to the name of the variable to which it was assigned.
|
|
|
|
Names are not required for classes, but they are recommended. For example,
|
|
consider what may happen when your class is output in an error message.
|
|
|
|
@float Figure, f:anon-err
|
|
@verbatim
|
|
// call non-existent method
|
|
foo.baz();
|
|
|
|
// TypeError: Object #<anonymous> has no method 'baz'
|
|
@end verbatim
|
|
@caption{Anonymous classes do not make for useful error messages}
|
|
@end float
|
|
|
|
If you have more than a couple classes in your software, that error message is
|
|
not too much help. You are left relying on the stack trace to track down the
|
|
error. This same output applies to converting a class to a string or viewing it
|
|
in a debugger. It is simply not helpful. If anything, it is confusing. If you've
|
|
debugged large JS applications that make liberal use of anonymous functions, you
|
|
might be able to understand that frustration.
|
|
|
|
Fortunately, ease.js permits you to declare a named class. A @dfn{named class}
|
|
is simply a class that is assigned a string for its name, so that error
|
|
messages, debuggers, etc provide more useful information. @emph{There is
|
|
functionally no difference between named and anonymous classes.}
|
|
|
|
@float Figure, f:class-named
|
|
@verbatim
|
|
var MyFoo = Class( 'MyFoo', {} ),
|
|
foo = MyFoo();
|
|
|
|
// call non-existent method
|
|
foo.baz();
|
|
|
|
// TypeError: Object #<MyFoo> has no method 'baz'
|
|
@end verbatim
|
|
@caption{Declaring an empty @emph{named} class}
|
|
@end float
|
|
|
|
Much better! We now have a useful error message and immediately know which class
|
|
is causing the issue.
|
|
|
|
@node Constructors
|
|
@subsection Constructors
|
|
In JavaScript, we are used to functions themselves being a constructor because
|
|
only constructors can be instantiated. With ease.js, constructors are handled in
|
|
a manner similar to most other languages, by providing a separate method. The
|
|
implementation ease.js chose is very similar to that of PHP's
|
|
(@pxref{Constructor Implementation}).
|
|
|
|
@float Figure, f:constructor
|
|
@verbatim
|
|
var Foo = Class( 'Foo',
|
|
{
|
|
'public __construct': function( name )
|
|
{
|
|
console.log( 'Hello, ' + name + '!' );
|
|
}
|
|
} );
|
|
|
|
// instantiate the class, invoking the constructor
|
|
Foo( 'World' );
|
|
|
|
// Output:
|
|
// Hello, World!
|
|
@end verbatim
|
|
@caption{Declaring constructors using ease.js}
|
|
@end float
|
|
|
|
When the class is instantiated, the constructor is invoked, permitting you do to
|
|
any necessary initialization tasks before the class can be used. The constructor
|
|
operates exactly how you would expect a constructor to in JavaScript, with one
|
|
major difference. Returning an object in the constructor does @emph{not} return
|
|
that object instead of the new class instance, since this does not make sense in
|
|
a Class-based model.
|
|
|
|
If you wish to prevent a class from being instantiated, simply throw an
|
|
exception within the constructor. This is useful if the class is intended to
|
|
provide only static methods, or if you wish to enforce a single instance
|
|
(one means of achieving a Singleton).
|
|
|
|
@float Figure, f:constructor-prevent
|
|
@verbatim
|
|
var Foo = Class( 'Foo',
|
|
{
|
|
'public __construct': function( name )
|
|
{
|
|
throw Error( "Cannot instantiate class Foo" );
|
|
}
|
|
} );
|
|
@end verbatim
|
|
@caption{Prevent class from being instantiated}
|
|
@end float
|
|
|
|
Constructors are optional. By default, nothing is done after the class is
|
|
instantiated.
|
|
|
|
@node Temporary Classes
|
|
@subsection Temporary Classes
|
|
In @ref{f:class-easejs,}, we saw that the @code{new} keyword was unnecessary
|
|
when instantiating classes. This permits a form of shorthand that is very useful
|
|
for creating @dfn{temporary classes}, or ``throwaway`` classes which are used
|
|
only once.
|
|
|
|
Consider the following example:
|
|
|
|
@float Figure, f:class-tmp
|
|
@verbatim
|
|
// new instance of anonymous class
|
|
var foo = Class(
|
|
{
|
|
'public bar': function()
|
|
{
|
|
return 'baz';
|
|
}
|
|
} )();
|
|
|
|
foo.bar(); // returns 'baz'
|
|
@end verbatim
|
|
@caption{Declaring a temporary (throwaway) class}
|
|
@end float
|
|
|
|
In @ref{f:class-tmp,} above, rather than declaring a class, storing that in a
|
|
variable, then instantiating it separately, we are doing it in a single command.
|
|
Notice the parenthesis at the end of the statement. This invokes the
|
|
constructor. Since the @code{new} keyword is unnecessary, a new instance of the
|
|
class is stored in the variable @var{foo}.
|
|
|
|
We call this a temporary class because it is used only to create a single
|
|
instance. The class is then never referenced again. Therefore, we needn't even
|
|
store it - it's throwaway.
|
|
|
|
The downside of this feature is that it is difficult to notice unless the reader
|
|
is paying very close attention. There is no keyword to tip them off. Therefore,
|
|
it is very important to clearly document that you are storing an instance in the
|
|
variable rather than an actual class definition. If you follow the CamelCase
|
|
convention for class names, then simply do not capitalize the first letter of
|
|
the destination variable for the instance.
|
|
|
|
@node Temporary Instances
|
|
@subsection Temporary Instances
|
|
Similar to @ref{Temporary Classes,}, you may wish to use an @emph{instance}
|
|
temporarily to invoke a method or chain of methods. @dfn{Temporary instances}
|
|
are instances that are instantiated in order to invoke a method or chain of
|
|
methods, then are immediately discarded.
|
|
|
|
@float Figure, f:inst-tmp
|
|
@verbatim
|
|
// retrieve the name from an instance of Foo
|
|
var name = Foo().getName();
|
|
|
|
// method chaining
|
|
var car = VehicleFactory().createBody().addWheel( 4 ).addDoor( 2 ).build();
|
|
|
|
// temporary class with callback
|
|
HttpRequest( host, port ).get( path, function( data )
|
|
{
|
|
console.log( data );
|
|
} );
|
|
|
|
// Conventionally (without ease.js), you'd accomplish the above using the
|
|
// 'new' keyword. You may still do this with ease.js, though it is less
|
|
// clean looking.
|
|
( new Foo() ).someMethod();
|
|
@end verbatim
|
|
@caption{Declaring a temporary (throwaway) class}
|
|
@end float
|
|
|
|
Rather than storing the class instance, we are using it simply to invoke
|
|
methods. The results of those methods are stored in the variable rather than the
|
|
class instance. The instance is immediately discarded, since it is no longer
|
|
able to be referenced, and is as such a temporary instance.
|
|
|
|
In order for method chaining to work, each method must return itself.
|
|
|
|
This pattern is useful for when a class requires instantiation in order to
|
|
invoke a method. Classes that intend to be frequently used in this manner should
|
|
declare static methods so that they may be accessed without the overhead of
|
|
creating a new class instance.
|
|
|
|
|
|
@node Inheritance
|
|
@section Inheritance
|
|
@table @code
|
|
@item C' = Class( string @var{name} ).extend( Object @var{base}, Object @var{dfn} )
|
|
Define named class @var{C'} identified by @var{name} as a subtype of @var{base},
|
|
described by @var{dfn}. @var{base} may be of type @code{Class} or may be any
|
|
enumerable object.
|
|
|
|
@item C' = C.extend( Object @var{dfn} )
|
|
Define anonymous class @var{C'} as a subtype of class @var{C}, described by
|
|
@var{dfn}.
|
|
|
|
@item C' = Class.extend( Object @var{base}, Object @var{dfn} )
|
|
Define anonymous class @var{C'} as a subtype of @var{base}, described by
|
|
@var{dfn}. @var{base} may be of type @code{Class} or may be any enumerable
|
|
object.
|
|
@end table
|
|
|
|
@var{C} is a class as defined in @ref{Defining Classes}. @var{base} may be any
|
|
class or object containing enumerable members. @var{dfn} is to be a definition
|
|
object as defined in @ref{dfnobj,,Definition Object}.
|
|
|
|
Provided @var{C} or @var{base} to satisfy requirements of @var{C}, class
|
|
@var{C'} will be defined as a @dfn{subtype} (child) of @dfn{supertype} (parent)
|
|
class @var{C}. Provided @var{base} that does @emph{not} satisfy requirements of
|
|
@var{C}, @var{C'} will be functionally equivalent to a subtype of anonymous
|
|
class @var{B} as defined by @var{B} = Class( @var{base} ).
|
|
|
|
|
|
@subsection Member Inheritance
|
|
Let @var{dfn\_n\^c} denote a member of @var{dfn} in regards to class @var{c}
|
|
that matches (case-sensitive) name @var{n} and is not private. Let @var{o\_n}
|
|
denote an override, represented as boolean value that is true under the
|
|
condition that both @var{dfn\_n\^C'} and @var{dfn\_n\^C} are defined
|
|
values.
|
|
|
|
@var{C'} will @dfn{inherit} all public and protected members of supertype
|
|
@var{C} such that @var{dfn\_n\^C'} = @var{dfn\_n\^C} for each @var{dfn\^C}. For
|
|
any positive condition @var{o\_n}, member @var{dfn\_n\^C'} will be said to
|
|
@dfn{override} member @var{dfn\_n\^C}, provided that overriding member @var{n}
|
|
passes all validation rules associated with the operation. A @code{protected}
|
|
member may be @dfn{escalated} to @code{public}, but the reverse is untrue.
|
|
@code{private} members are invisible to subtypes.
|
|
|
|
For any positive condition @var{o\_n} where member @var{n} is defined as a
|
|
@emph{method}:
|
|
|
|
@itemize
|
|
@item
|
|
One of the following conditions must always be true:
|
|
@itemize
|
|
@item
|
|
@var{dfn\_n\^C} is declared with the @ref{Member Keywords,,@code{virtual}}
|
|
keyword and @var{dfn\_n\^C'} is declared with the
|
|
@ref{Member Keywords,,@code{override}} keyword.
|
|
@itemize
|
|
@item
|
|
Note that @var{dfn\_n\^C'} will not become @ref{Member
|
|
Keywords,,@code{virtual}} by default (unlike languages such as C++); they
|
|
must be explicitly declared as such.
|
|
@end itemize
|
|
@item
|
|
@var{dfn\_n\^C} is declared with the @ref{Member Keywords,,@code{abstract}}
|
|
keyword and @var{dfn\_n\^C'} omits the @ref{Member Keywords,,@code{override}}
|
|
keywords.
|
|
@end itemize
|
|
@item
|
|
The argument count of method @var{dfn\_n\^C'} must be >= the argument count of
|
|
method @var{dfn\_n\^C} to permit polymorphism.
|
|
@item
|
|
A reference to super method @var{dfn\_n\^C} will be preserved and assigned to
|
|
@samp{this.__super} within context of method @var{dfn\_n\^C'}.
|
|
@item
|
|
A method is said to be @dfn{concrete} when it provides a definition and
|
|
@dfn{abstract} when it provides only a declaration
|
|
(@pxref{dfnobj,,Definition Object}).
|
|
@itemize
|
|
@item
|
|
Any method @var{n} such that @var{dfn\_n\^C} is declared @code{abstract} may
|
|
be overridden by a concrete or abstract method @var{dfn\_n\^C'}.
|
|
@item
|
|
A method @var{n} may @emph{not} be declared @ref{Member
|
|
Keywords,,@code{abstract}} if @var{dfn\_n\^C} is concrete.
|
|
@end itemize
|
|
@item
|
|
Member @var{dfn\_n\^C} must be a method.
|
|
@end itemize
|
|
|
|
Members that have been declared @code{static} cannot be overridden
|
|
(@pxref{Static Members}).
|
|
|
|
@subsection Discussion
|
|
Inheritance can be a touchy subject among many Object-Oriented developers due to
|
|
encapsulation concerns and design considerations over method overrides. The
|
|
decision of whether or not inheritance is an appropriate choice over composition
|
|
is left to the developer; ease.js provides the facilities for achieving
|
|
classical inheritance where it is desired.
|
|
|
|
@float Figure, f:inheritance-ex
|
|
@image{img/inheritance-ex}
|
|
@caption{Basic inheritance example}
|
|
@end float
|
|
|
|
In the above example, we would say that @var{LazyDog} and @var{TwoLeggedDog}
|
|
are @emph{subtypes} of @var{Dog}, and that @var{Dog} is the @emph{supertype}
|
|
of the two. We describe inheritance as an ``is a'' relationship. That is:
|
|
|
|
@itemize
|
|
@item
|
|
@var{LazyDog} is a @var{Dog}.
|
|
|
|
@item
|
|
@var{TwoLeggedDog} is also a @var{Dog}.
|
|
|
|
@item
|
|
@var{Dog} is @emph{not} a @var{LazyDog} or a @var{TwoLeggedDog}.
|
|
@end itemize
|
|
|
|
Subtypes @dfn{inherit} all public and protected members of their supertypes
|
|
(@pxref{Access Modifiers}). This means that, in the case of our above example,
|
|
the @code{walk()} and @code{bark()} methods would be available to our subtypes.
|
|
If the subtype also defines a method of the same name, as was done above, it
|
|
will @dfn{override} the parent functionality. For now, we will limit our
|
|
discussion to public members. How would we represent these classes using
|
|
ease.js?
|
|
|
|
@float Figure, f:inheritance
|
|
@verbatim
|
|
// our parent class (supertype)
|
|
var Dog = Class( 'Dog',
|
|
{
|
|
'virtual public walk': function()
|
|
{
|
|
console.log( 'Walking the dog' );
|
|
},
|
|
|
|
'public bark': function()
|
|
{
|
|
console.log( 'Woof!' );
|
|
}
|
|
} );
|
|
|
|
// subclass (child), as a named class
|
|
var LazyDog = Class( 'LazyDog' ).extend( Dog,
|
|
{
|
|
'override public walk': function()
|
|
{
|
|
console.log( 'Lazy dog refuses to walk.' );
|
|
}
|
|
} );
|
|
|
|
// subclass (child), as an anonymous class
|
|
var TwoLeggedDog = Dog.extend(
|
|
{
|
|
'override public walk': function()
|
|
{
|
|
console.log( 'Walking the dog on two feet' );
|
|
}
|
|
} );
|
|
@end verbatim
|
|
@caption{Inheritance in ease.js}
|
|
@end float
|
|
|
|
You should already understand how to define a class (@pxref{Defining Classes}).
|
|
The above example introduced two means of @dfn{extending} classes -- defining a
|
|
new class that inherits from a parent:
|
|
|
|
@table @strong
|
|
@item Named Subclasses
|
|
@var{LazyDog} is defined as a @emph{named} subclass
|
|
(@pxref{Anonymous vs. Named Classes}). This syntax requires the use of
|
|
@samp{Class( 'Name' )}. The @code{extend()} method then allows you to extend
|
|
from an existing class by passing the class reference in as the first argument.
|
|
|
|
@item Anonymous Subclasses
|
|
@var{TwoLeggedDog} was declared as an @emph{anonymous} subclass. The syntax for
|
|
this declaration is a bit more concise, but you forfeit the benefits of named
|
|
classes (@pxref{Anonymous vs. Named Classes}). In this case, you can simply call
|
|
the supertype's @code{extend()} method. Alternatively, you can use the
|
|
@samp{Class.extend( Base, @{@} )} syntax, as was used with the named subclass
|
|
@var{LazyDog}.
|
|
@end table
|
|
|
|
You are @emph{always} recommended to use the named syntax when declaring classes
|
|
in order to provide more useful error messages. If you are willing to deal with
|
|
the less helpful error messages, feel free to use anonymous classes for their
|
|
conciseness.
|
|
|
|
@menu
|
|
* Understanding Member Inheritance:: How to work with inherited members
|
|
* Overriding Methods:: Overriding inherited methods
|
|
* Type Checks and Polymorphism:: Substituting similar classes for
|
|
one-another
|
|
* Visibility Escalation:: Increasing visibility of inherited members
|
|
@end menu
|
|
|
|
@node Understanding Member Inheritance
|
|
@subsection Understanding Member Inheritance
|
|
In @ref{f:inheritance}, we took a look at how to inherit from a parent class.
|
|
What does it mean when we ``inherit'' from a parent? What are we inheriting? The
|
|
answer is: the API.
|
|
|
|
There are two types of APIs that subtypes can inherit from their parents:
|
|
|
|
@table @emph
|
|
@item Public API
|
|
This is the API that is accessible to everyone using your class. It contains all
|
|
public members. We will be focusing on public members in this chapter.
|
|
|
|
@item Protected API
|
|
Protected members make up a protected API, which is an API available to
|
|
subclasses but @emph{not} the outside world. This is discussed more in the
|
|
Access Modifiers section (@pxref{Access Modifiers}), so we're going to leave
|
|
this untouched for now.
|
|
@end table
|
|
|
|
When a subtype inherits a member from its parent, it acts almost as if that
|
|
member was defined in the class itself@footnote{This statement is not to imply
|
|
that inheritance is a case of copy-and-paste. There are slight variations, which
|
|
are discussed in more detail in the Access Modifiers section (@pxref{Access
|
|
Modifiers}).}. This means that the subtype can use the inherited members as if
|
|
they were its own (keep in mind that members also include properties). This
|
|
means that we @emph{do not} have to redefine the members in order to use them
|
|
ourselves.
|
|
|
|
@var{LazyDog} and @var{TwoLeggedDog} both inherit the @code{walk()} and
|
|
@code{bark()} methods from the @var{Dog} supertype. Using @var{LazyDog} as an
|
|
example, let's see what happens when we attempt to use the @code{bark()} method
|
|
inherited from the parent.
|
|
|
|
@float Figure, f:using-inherited-members
|
|
@verbatim
|
|
var LazyDog = Class( 'LazyDog' ).extend( Dog,
|
|
{
|
|
/**
|
|
* Bark when we're poked
|
|
*/
|
|
'public poke': function()
|
|
{
|
|
this.bark();
|
|
}
|
|
} );
|
|
|
|
// poke() a new instance of LazyDog
|
|
LazyDog().poke();
|
|
|
|
// Output:
|
|
// Woof!
|
|
@end verbatim
|
|
@caption{Using inherited members}
|
|
@end float
|
|
|
|
In @ref{f:using-inherited-members} above, we added a @code{poke()} method to our
|
|
@var{LazyDog} class. This method will call the @code{bark()} method that was
|
|
inherited from @var{Dog}. If we actually run the example, you will notice that
|
|
the dog does indeed bark, showing that we are able to call our parent's method
|
|
even though we did not define it ourselves.
|
|
|
|
@node Overriding Methods
|
|
@subsection Overriding Methods
|
|
When a method is inherited, you have the option of either keeping the parent's
|
|
implementation or overriding it to provide your own. When you override a method,
|
|
you replace whatever functionality was defined by the parent. This concept was
|
|
used to make our @var{LazyDog} lazy and our @var{TwoLeggedDog} walk on two legs
|
|
in @ref{f:inheritance}.
|
|
|
|
After overriding a method, you may still want to invoke the parent's method.
|
|
This allows you to @emph{augment} the functionality rather than replacing it
|
|
entirely. ease.js provides a magic @code{__super()} method to do this. This
|
|
method is defined only for the overriding methods and calls the parent method
|
|
that was overridden.
|
|
|
|
In order to demonstrate this, let's add an additional subtype to our hierarchy.
|
|
@var{AngryDog} will be a subtype of @var{LazyDog}. Not only is this dog lazy,
|
|
but he's rather moody.
|
|
|
|
@float Figure, f:super-method
|
|
@verbatim
|
|
var AngryDog = Class( 'AngryDog' ).extend( LazyDog,
|
|
{
|
|
'public poke': function()
|
|
{
|
|
// augment the parent method
|
|
console.log( 'Grrrrrr...' );
|
|
|
|
// call the overridden method
|
|
this.__super();
|
|
}
|
|
} );
|
|
|
|
// poke a new AngryDog instance
|
|
AngryDog().poke();
|
|
|
|
// Output:
|
|
// Grrrrrr...
|
|
// Woof!
|
|
@end verbatim
|
|
@caption{Using @code{__super()} method}
|
|
@end float
|
|
|
|
If you remember from @ref{f:using-inherited-members}, we added a @code{poke()}
|
|
method to @var{LazyDog}. In @ref{f:super-method} above, we are overriding this
|
|
method so that @var{AngryDog} growls when you poke him. However, we still want
|
|
to invoke @var{LazyDog}'s default behavior when he's poked, so we also call the
|
|
@code{__super()} method. This will also make @var{AngryDog} bark like
|
|
@var{LazyDog}.
|
|
|
|
It is important to note that @code{__super()} must be invoked like any other
|
|
method. That is, if the overridden method requires arguments, you must pass them
|
|
to @code{__super()}. This allows you to modify the argument list before it is
|
|
sent to the overridden method.
|
|
|
|
@node Type Checks and Polymorphism
|
|
@subsection Type Checks and Polymorphism
|
|
The fact that the API of the parent is inherited is a very important detail. If
|
|
the API of subtypes is guaranteed to be @emph{at least} that of the parent, then
|
|
this means that a function expecting a certain type can also work with any
|
|
subtypes. This concept is referred to as @dfn{polymorphism}, and is a very
|
|
powerful aspect of Object-Oriented programming.
|
|
|
|
Let's consider a dog trainer. A dog trainer can generally train any type of dog
|
|
(technicalities aside), so it would stand to reason that we would want our dog
|
|
trainer to be able to train @var{LazyDog}, @var{AngryDog}, @var{TwoLeggedDog},
|
|
or any other type of @var{Dog} that we may throw at him/her.
|
|
|
|
@float Figure, f:polymorphism-uml
|
|
@image{img/composition-uml}
|
|
@caption{Class structure to demonstrate polymorphism}
|
|
@end float
|
|
|
|
Type checks are traditionally performed in JavaScript using the
|
|
@code{instanceOf} operator. While this can be used in most inheritance cases
|
|
with ease.js, it is not recommended. Rather, you are encouraged to use ease.js's
|
|
own methods for determining instance type@footnote{The reason for this will
|
|
become clear in future chapters. ease.js's own methods permit checking for
|
|
additional types, such as Interfaces.}. Support for the @code{instanceOf}
|
|
operator is not guaranteed.
|
|
|
|
Instead, you have two choices with ease.js:
|
|
|
|
@table @code
|
|
@item Class.isInstanceOf( type, instance );
|
|
Returns @code{true} if @var{instance} is of type @var{type}. Otherwise, returns
|
|
@code{false}.
|
|
|
|
@item Class.isA( type, instance );
|
|
Alias for @code{Class.isInstanceOf()}. Permits code that may read better
|
|
depending on circumstance and helps to convey the ``is a'' relationship that
|
|
inheritance creates.
|
|
@end table
|
|
|
|
For example:
|
|
|
|
@float Figure, f:instanceof-ex
|
|
@verbatim
|
|
var dog = Dog()
|
|
lazy = LazyDog(),
|
|
angry = AngryDog();
|
|
|
|
Class.isInstanceOf( Dog, dog ); // true
|
|
Class.isA( Dog, dog ); // true
|
|
Class.isA( LazyDog, dog ); // false
|
|
Class.isA( Dog, lazy ); // true
|
|
Class.isA( Dog, angry ); // true
|
|
|
|
// we must check an instance
|
|
Class.isA( Dog, LazyDog ); // false; instance expected, class given
|
|
@end verbatim
|
|
@caption{Using ease.js to determine instance type}
|
|
@end float
|
|
|
|
It is important to note that, as demonstrated in @ref{f:instanceof-ex} above, an
|
|
@emph{instance} must be passed as a second argument, not a class.
|
|
|
|
Using this method, we can ensure that the @var{DogTrainer} may only be used with
|
|
an instance of @var{Dog}. It doesn't matter what instance of @var{Dog} - be it a
|
|
@var{LazyDog} or otherwise. All that matters is that we are given a @var{Dog}.
|
|
|
|
@float Figure, f:polymorphism-easejs
|
|
@verbatim
|
|
var DogTrainer = Class( 'DogTrainer',
|
|
{
|
|
'public __construct': function( dog )
|
|
{
|
|
// ensure that we are given an instance of Dog
|
|
if ( Class.isA( Dog, dog ) === false )
|
|
{
|
|
throw TypeError( "Expected instance of Dog" );
|
|
}
|
|
}
|
|
} );
|
|
|
|
// these are all fine
|
|
DogTrainer( Dog() );
|
|
DogTrainer( LazyDog() );
|
|
DogTrainer( AngryDog() );
|
|
DogTrainer( TwoLeggedDog() );
|
|
|
|
// this is not fine; we're passing the class itself
|
|
DogTrainer( LazyDog );
|
|
|
|
// nor is this fine, as it is not a dog
|
|
DogTrainer( {} );
|
|
@end verbatim
|
|
@caption{Polymorphism in ease.js}
|
|
@end float
|
|
|
|
It is very important that you use @emph{only} the API of the type that you are
|
|
expecting. For example, only @var{LazyDog} and @var{AngryDog} implement a
|
|
@code{poke()} method. It is @emph{not} a part of @var{Dog}'s API. Therefore, it
|
|
should not be used in the @var{DogTrainer} class. Instead, if you wished to use
|
|
the @code{poke()} method, you should require that an instance of @var{LazyDog}
|
|
be passed in, which would also permit @var{AngryDog} (since it is a subtype of
|
|
@var{LazyDog}).
|
|
|
|
Currently, it is necessary to perform this type check yourself. In future
|
|
versions, ease.js will allow for argument type hinting/strict typing, which will
|
|
automate this check for you.
|
|
|
|
@node Visibility Escalation
|
|
@subsection Visibility Escalation
|
|
@dfn{Visibility escalation} is the act of increasing the visibility of a member.
|
|
Since private members cannot be inherited, this essentially means making a
|
|
protected member public.
|
|
|
|
If you override a protected method, you may increase its visibility to public
|
|
without any problems. If you follow the convention of prefixing private members
|
|
with an underscore, you may find that it's not recommended doing so for
|
|
protected members. This is because subtypes may decide to make the member
|
|
public.
|
|
|
|
In order to increase the visibility, you do have to override the member. For
|
|
properties, this has no discernible effect; you're just redefining it. For
|
|
methods, this means that you are overriding the entire body. Therefore, you will
|
|
either have to provide an alternate implementation, or call
|
|
@samp{this.__super()} to invoke the original method.
|
|
|
|
Note that @emph{you cannot go from public to protected}. This will throw an
|
|
error. You can only increase the level of visibility. This ensures that once a
|
|
class defines an API, subclasses cannot alter it. That API is forever for all
|
|
subtypes. This means that, if you are expecting a certain type, you can rest
|
|
assured that whatever you are given, even if it is a subtype, has the API you
|
|
are expecting.
|
|
|
|
Let's take a look at an example.
|
|
|
|
@float Figure, f:vis-esc
|
|
@verbatim
|
|
var Foo = Class(
|
|
{
|
|
'protected canEscalate': 'baz',
|
|
|
|
'protected escalateMe': function( arg )
|
|
{
|
|
console.log( 'In escalateMe' );
|
|
},
|
|
|
|
'public cannotMakeProtected': function()
|
|
{
|
|
}
|
|
} ),
|
|
|
|
SubFoo = Foo.extend(
|
|
{
|
|
/**
|
|
* Escalating a property means redefining it
|
|
*/
|
|
'public canEscalate': 'baz',
|
|
|
|
/**
|
|
* We can go protected -> public
|
|
*/
|
|
'public escalateMe': function( arg )
|
|
{
|
|
// simply call the parent method
|
|
this.__super( arg );
|
|
}
|
|
} );
|
|
@end verbatim
|
|
@caption{Visibility can be escalated}
|
|
@end float
|
|
|
|
Note that, in the above example, making the public @var{cannotMakeProtected}
|
|
method protected would throw an error.
|
|
|
|
|
|
@node Static Members
|
|
@section Static Members
|
|
@dfn{Static members} do not require instantiation of the containing class in
|
|
order to be used, but may also be called by instances. They are attached to the
|
|
class itself rather than an instance. Static members provide convenience under
|
|
certain circumstances where class instantiation is unnecessary and permit
|
|
sharing data between instances of a class. However, static members, when used
|
|
improperly, can produce poorly designed classes and tightly coupled code that is
|
|
also difficult to test. Static properties also introduce problems very similar
|
|
to global variables.
|
|
|
|
Let us consider an implementation of the factory pattern. Class @var{BigBang}
|
|
will declare two static methods in order to satisfy different means of
|
|
instantiation: @code{fromBraneCollision()} and @code{fromBigCrunch()} (for the
|
|
sake of the example, we're not going to address every theory). Let us also
|
|
consider that we want to keep track of the number of big bangs in our universe
|
|
(perhaps to study whether or not a "Big Crunch" could have potentially
|
|
happened in the past) by incrementing a counter each time a new big bang occurs.
|
|
Because we are using a static method, we cannot use a property of an instance in
|
|
order to store this data. Therefore, we will use a static property of class
|
|
@var{BigBang}.
|
|
|
|
@float Figure, f:static-ex
|
|
@verbatim
|
|
var BigBang = Class( 'BigBang',
|
|
{
|
|
/**
|
|
* Number of big bangs that has occurred
|
|
* @type {number}
|
|
*/
|
|
'private static _count': 0,
|
|
|
|
/**
|
|
* String representing the type of big bang
|
|
* @type {string}
|
|
*/
|
|
'private _type': '',
|
|
|
|
|
|
/**
|
|
* Create a new big bang from the collision of two membranes
|
|
*
|
|
* @return {BraneSet} the set of branes that collided
|
|
*
|
|
* @return {BigBang} new big bang
|
|
*/
|
|
'public static fromBraneCollision': function( brane_set )
|
|
{
|
|
// do initialization tasks...
|
|
|
|
return BigBang( 'brane', brane_set.getData() );
|
|
},
|
|
|
|
|
|
/**
|
|
* Create a new big bang following a "Big Crunch"
|
|
*
|
|
* @param {BigCrunch} prior crunch
|
|
*
|
|
* @return {BigBang} new big bang
|
|
*/
|
|
'public static fromBigCrunch': function( crunch )
|
|
{
|
|
// do initialization tasks...
|
|
|
|
return BigBang( 'crunch', crunch.getData() );
|
|
},
|
|
|
|
|
|
/**
|
|
* Returns the total number of big bangs that have occurred
|
|
*
|
|
* @return {number} total number of big bangs
|
|
*/
|
|
'public static getTotalCount': function()
|
|
{
|
|
return this.$('_count');
|
|
}
|
|
|
|
|
|
/**
|
|
* Construct a new big bang
|
|
*
|
|
* @param {string} type big bang type
|
|
* @param {object} data initialization data
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
'public __construct': function( type, data )
|
|
{
|
|
this._type = type;
|
|
|
|
// do complicated stuff with data
|
|
|
|
// increment big bang count
|
|
this.__self.$( '_count',
|
|
this.__self.$('count') + 1
|
|
);
|
|
},
|
|
} );
|
|
|
|
// create one of each
|
|
var brane_bang = BigBang.fromBraneCollision( branes ),
|
|
crunch_bang = BigBang.fromBigCrunch( crunch_incident );
|
|
|
|
console.log( "Total number of big bangs: %d", BigBang.getTotalCount() );
|
|
// Total number of big bangs: 2
|
|
@end verbatim
|
|
@caption{Static member example using the factory pattern}
|
|
@end float
|
|
|
|
Due to limitations of pre-ECMAScript 5 implementations, ease.js's static
|
|
implementation must be broken into two separate parts: properties and methods.
|
|
|
|
@menu
|
|
* Static Methods::
|
|
* Static Properties::
|
|
* Constants:: Immutable static properties
|
|
@end menu
|
|
|
|
@node Static Methods
|
|
@subsection Static Methods
|
|
In @ref{f:static-ex}, we implemented three static methods: two factory methods,
|
|
@code{fromBraneCollision()} and @code{FromBigCrunch()}, and one getter method to
|
|
retrieve the total number of big bangs, @code{getTotalCount()}. These methods are
|
|
very similar to instance methods we are already used to, with a few important
|
|
differences:
|
|
|
|
@enumerate
|
|
@item
|
|
Static methods are declared with the @code{static} keyword.
|
|
|
|
@item
|
|
In the body, @code{this} is bound to the class itself, rather than the instance.
|
|
|
|
@item
|
|
Static methods cannot call any non-static methods of the same class without
|
|
first instantiating it.
|
|
@end enumerate
|
|
|
|
The final rule above is not true when the situation is reversed. Non-static
|
|
methods @emph{can} call static methods through use of the @var{__self} object,
|
|
which is a reference to the class itself. That is, @var{this} in a static method
|
|
is the same object as @var{this.__self} in a non-static method. This is
|
|
demonstrated by @code{getTotalCount()}
|
|
|
|
@verbatim
|
|
this.$('_count')
|
|
@end verbatim
|
|
|
|
and @code{__construct()}.
|
|
|
|
@verbatim
|
|
this.__self.$('_count')
|
|
@end verbatim
|
|
|
|
To help remember @var{__self}, consider what the name states. A class is a
|
|
definition used to create an object. The body of a method is a definition, which
|
|
is defined on the class. Therefore, even though the body of a method may be
|
|
called in the context of an instance, it is still part of the class. As such,
|
|
@var{__self} refers to the class.
|
|
|
|
@node Static Properties
|
|
@subsection Static Properties
|
|
You have likely noticed by now that static properties are handled a bit
|
|
differently than both static methods and non-static properties. This difference
|
|
is due to pre-ECMAScript 5 limitations and is discussed at length in the
|
|
@ref{Static Implementation} section.
|
|
|
|
Static properties are read from and written to using the @dfn{static accessor
|
|
method} @code{$()}. This method name was chosen because the @code{$} prefix is
|
|
common in scripting languages such as BASH, Perl (for scalars) and PHP. The
|
|
accessor method accepts two arguments, the second being optional. If only the
|
|
first argument is provided, the accessor method acts as a getter, as in
|
|
@ref{f:static-ex}'s @code{getTotalCount()}:
|
|
|
|
@verbatim
|
|
return this.$('_count');
|
|
@end verbatim
|
|
|
|
If the second argument is provided, it acts as a setter, as in
|
|
@code{__construct()}:
|
|
|
|
@verbatim
|
|
this.__self.$( '_count',
|
|
this.__self.$('count') + 1
|
|
);
|
|
@end verbatim
|
|
|
|
Setting @code{undefined} values is supported. The @code{delete} operator is not
|
|
supported, as its use is both restricted by the language itself and doesn't make
|
|
sense to use in this context. As hinted by the example above, the increment and
|
|
decrement operators (@code{++} and @code{--}) are not supported because
|
|
JavaScript does not permit returning values by reference.
|
|
|
|
It is important to understand that, currently, the accessor method cannot be
|
|
omitted. Consider the following example:
|
|
|
|
@float Figure, f:static-accessor
|
|
@verbatim
|
|
var Foo = Class( 'Foo',
|
|
{
|
|
'public static bar': 'baz',
|
|
},
|
|
|
|
SubFoo = Class( 'SubFoo' ).extend( Foo, {} )
|
|
;
|
|
|
|
// correct
|
|
Foo.$( 'bar, 'baz2' );
|
|
Foo.$('bar'); // baz2
|
|
SubFoo.$('bar'); // baz2
|
|
SubFoo.$( 'bar', 'baz3' );
|
|
Foo.$('bar'); // baz3
|
|
|
|
// INCORRECT
|
|
Foo.bar = 'baz2';
|
|
Foo.bar; // baz2
|
|
SubFoo.bar; // undefined
|
|
@end verbatim
|
|
@caption{Static accessor method cannot be omitted}
|
|
@end float
|
|
|
|
@node Constants
|
|
@subsection Constants
|
|
@dfn{Constants}, in terms of classes, are immutable static properties. This
|
|
means that, once defined, a constant cannot be modified. Since the value is
|
|
immutable, it does not make sense to create instances of the property. As such,
|
|
constant values are implicitly static. This ensures that each instance, as well
|
|
as any static access, references the exact same value. This is especially
|
|
important for objects and arrays.
|
|
|
|
One important difference between other languages, such as PHP, is that ease.js
|
|
supports the @ref{Access Modifiers, visibility modifiers} in conjunction with
|
|
the @code{const} keyword. That is, you can have public, protected and private
|
|
constants. Constants are public by default, like every other type of member.
|
|
This feature permits encapsulating constant values, which is important if you
|
|
want an immutable value that shouldn't be exposed to the rest of the world (e.g.
|
|
a service URL, file path, etc). Consider the following example in which we have
|
|
a class responsible for reading mount mounts from @file{/etc/fstab}:
|
|
|
|
@float Figure, f:const-ex
|
|
@verbatim
|
|
Class( 'MountPointIterator',
|
|
{
|
|
'private const _PATH': '/etc/fstab',
|
|
|
|
'private _mountPoints': [],
|
|
|
|
|
|
'public __construct': function()
|
|
{
|
|
var data = fs.readFileSync( this.$('_PATH') );
|
|
this._parseMountPoints( data );
|
|
},
|
|
|
|
// ...
|
|
} );
|
|
@end verbatim
|
|
@caption{Using the @code{const} keyword}
|
|
@end float
|
|
|
|
In the above example, attempting to access the @var{_PATH} constant from outside
|
|
the class would return @code{undefined}. Had the constant been declared as
|
|
public, or had the visibility modifier omitted, it could have been accessed just
|
|
like any other static property:
|
|
|
|
@verbatim
|
|
// if PATH were a public constant value
|
|
MountPointIterator.$('PATH');
|
|
@end verbatim
|
|
|
|
Any attempts to modify the value of a constant will result in an exception. This
|
|
will also work in pre-ES5 engines due to use of the @ref{Static Properties,
|
|
static accessor method} (@code{$()}).
|
|
|
|
It is important to note that constants prevent the @emph{value of the property}
|
|
from being reassigned. It @emph{does not} prevent modification of the value that
|
|
is @emph{referenced} by the property. For example, if we had a constant
|
|
@var{foo}, which references an object, such that
|
|
|
|
@verbatim
|
|
'const foo': { a: 'b' }
|
|
@end verbatim
|
|
|
|
it is perfectly legal to alter the object:
|
|
|
|
@verbatim
|
|
MyClass.$('foo').a = 'c';
|
|
@end verbatim
|
|
|