In enchant.js | Fundamentals, we created a rain-collection game in which we created game elements with the help of factory functions. The factory functions (one of which is reproduced below) returned Sprite instances with some extra properties and some additional things preconfigured.
The above is an example of an approach to object oriented programming called prototypal object orientation. JavaScript was designed to be prototypal.
In the simplest kind of prototypal object orientation, you first create an instance of an object that is close to what you need and then you modify that instance as needed for your purposes. The additions and modification happen after the root object is created. The factory function above does exactly that. (A factory function is just a function that returns a new object instance.)
You can think of the droplet
returned by makeDroplet(collector)
as
being a special kind of Sprite
—one that:
'droplet24.png'
),gravity
property,rootScene
,game.isStarted
flag,
moves itself, and checks for collisions between itself and a collector
.When you call the makeDroplet(collector)
factory function, the
function creates a Sprite
instance, makes the necessary modifications
to that instance, and then returns the modified instance.
You don't need to put the Sprite instantiation and subsequent modification inside a function. The process would still be prototypal if you didn't. The reason we put it all into a function was to make the code clearer and more modular.
There is another kind of object-oriented programming called class-based object orientation. Many popular object-oriented programming languages (e.g., C++, Java, C#) are class-based rather than prototypal, so many people are more comfortable working in a class-based paradigm rather than a prototypal one. JavaScript normally doesn't support class-based object orientation, but the designers of enchant.js have built basic support for class-based object orientation into the framework.
The underlying idea of class-based object orientation is that rather than
create an object instance and modify it after you create it, you write
definitions for classes of objects that define what an instance of an
object of that class should be from the start—sort of like writing
a blueprint. The "blueprint" is a class definition. When you
instantiate the object from the "blueprint" (using the new
operator
in JavaScript) it's ready to go.
But there's a little more to it.
As far as our droplets
are concerned, we have already discussed
how they are a specialized kind of Sprite
—kind of like
how a Subaru Forester 2.5i Premium is a specialized kind of Subaru Forester,
which is itself a specialized kind of SUV, which is itself a specialized
kind of vehicle.
Let's assume that someone has already created a "blueprint" for
the Subaru_Forester
. Given that a Subaru_Forester_2.5i_Premium
(a
class of object) is a specialized kind of Subaru_Forester
(another
class of object), a really easy way to create a "blueprint"
for a Subaru_Forester_2.5i_Premium
would be to say something
like: Subaru_Forester_2.5i_Premium
is a Subaru_Forester
that:
Once we have the made the new "blueprint", we can start to make
instances of Subaru_Forester_2.5i_Premium
.
When you define things in this way, that is, creating a base "blueprint"
and then using additional "blueprints" to show only how more
specialized versions are different from the base, you are using inheritance.
Inheritance is a feature of class-based object orientation. We would
say that Subaru_Forester_2.5i_Premium
inherits
from Subaru_Forester
. All the things that are in the definition
of a Subaru_Forester
also go for a Subaru_Forester_2.5i_Premium
,
except for the things you have added to changed in the Subaru_Forester_2.5i_Premium
definition.
If you were to take a class-based view of a droplet in our program, you
would view a single droplet as an instance of the Droplet
class
and that Droplet
is a specialized kind of Sprite
(i.e.,
the Droplet
class inherits from the Sprite
class.)
In the class definition, you would want to indicate that Droplet
inherits
from Sprite
and then define how a Droplet
differs
from a Sprite
(i.e., what additions and modifications are
needed).
The key difference between protoypal thinking and class-based thinking is that protoypal thinking takes a make-and-modify approach whereas class-based thinking takes a what-is-it approach. It's really just a small shift in perspective.
In the code below, we have rewritten the prototypal makeDroplet(collector)
factory
function as an equivalent class definition. The syntax,
var Droplet = Class.create(Sprite, ...
says that Droplet
is a class that inherits from the Sprite
class—meaning
that a Droplet
is a special kind of Sprite
. The initialize
method
is run automatically when you create an instance (which we will show
you how to do in a bit). Inside the initialize
method, the
JavaScript keyword this
is used to mean "this instance".
To create an instance of the Droplet class using enchant.js' class, you would write something like:
var myDroplet = new Droplet(collector);
Neither prototypal nor class-based object oriention can be considered superior to the other. When it comes to programming in enchant.js, whether you use prototypal or class-based object orientation is mostly a matter of taste.
For completeness, here is the complete game rewritten to use classes rather than the prototypal factory functions.