1
0
Fork 0
easejs/doc/classes.texi

291 lines
11 KiB
Plaintext
Raw Normal View History

@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
2011-03-14 18:04:46 -04:00
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
2011-03-14 18:04:46 -04:00
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.
2011-03-14 18:04:46 -04:00
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();
2011-03-14 18:04:46 -04:00
console.log( foo.getProp() ); // outputs "foobar"
@end verbatim
2011-03-14 18:04:46 -04:00
@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
2011-03-14 18:04:46 -04:00
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
* Declaring Classes:: Learn how to declare a class with ease.js
@end menu
@node Declaring Classes
@section Declaring Classes
We just took a look at what it's like declaring a class using prototypes
2011-03-14 18:04:46 -04:00
(@pxref{f:class-js,}). This method is preferred for many developers, but it is
important to recognize that there is a distinct difference between Prototypal
and Object-Oriented development models. 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
2011-03-14 18:04:46 -04:00
// if client-side, use: var Class = easejs.Class;
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();
2011-03-14 18:04:46 -04:00
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
2011-03-14 18:04:46 -04:00
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
2011-03-14 18:04:46 -04:00
if you wish, but it is optional when using ease.js. This is mainly because
2011-03-14 18:48:49 -04:00
without this feature, if the keyword is omitted, the constructor is called as a
2011-03-14 18:04:46 -04:00
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::
* 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}
2011-03-14 18:04:46 -04:00
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.
2011-03-15 00:31:09 -04:00
@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.