In this article, learn about OOP in JavaScript, and explore the prototypical and classical inheritance models. Examples explain common patterns in games that can greatly benefit from the structure and maintainability of OOP design. The ultimate goal is that each piece of code is human-readable and represents an idea and a single purpose, altogether transcending a set of instructions and algorithms to become a finely-tuned work of art.
The goals of OOP are to provide data abstraction, modularity, encapsulation, polymorphism, and inheritance. With OOP, you can abstract the concept of the code from the authoring of code, thereby providing elegance, reusability, and readability at the cost of file count, line count, and (if poorly managed) performance.
Traditionally, game developers shied away from pure OOP approaches so they could squeeze every bit of performance possible from the CPU cycle. Many JavaScript game tutorials use non-OOP approaches for a quick demo rather than providing a solid foundation. JavaScript developers have different problems than developers of other gaming environments: memory is not manually managed, and JavaScript files are executed in a global context that encourages maintainability nightmares with spaghetti code, namespace collisions, and mazes of if/else statements. To get the most out of developing a JavaScript game, follow OOP best practices to significantly enhance the future maintainability, pace of development, and expressiveness of your game.
Unlike languages that use classical inheritance, in JavaScript there is no
built-in class construct. Functions are first-class citizens of the
JavaScript world and, like all user-defined objects, they have prototypes.
Calling a function with the new keyword
actually creates a copy of the function's prototype object and uses that
object as the context for the keyword this
inside of the function. Listing 1 shows an example.
Listing 1. Constructing an object with prototypes
// constructor function
function MyExample() {
// property of an instance when used with the 'new' keyword
this.isTrue = true;
};
MyExample.prototype.getTrue = function() {
return this.isTrue;
}
MyExample();
// here, MyExample was called in the global context,
// so the window object now has an isTrue property—this is NOT a good practice
MyExample.getTrue;
// this is undefined—the getTrue method is a part of the MyExample prototype,
// not the function itself
var example = new MyExample();
// example is now an object whose prototype is MyExample.prototype
example.getTrue; // evaluates to a function
example.getTrue(); // evaluates to true because isTrue is a property of the
// example instance
|
Conventionally, functions that represent a class should start with a capital letter to signify that it is meant to be a constructor. The name should represent the data structure it creates.
The magic of creating instances of classes is a combination of the new keyword and prototype objects. Prototype objects can have both methods and properties, as shown in Listing 2.
Listing 2. Simple inheritance through prototyping
// Base class
function Character() {};
Character.prototype.health = 100;
Character.prototype.getHealth = function() {
return this.health;
}
// Inherited classes
function Player() {
this.health = 200;
}
Player.prototype = new Character;
function Monster() {}
Monster.prototype = new Character;
var player1 = new Player();
var monster1 = new Monster();
player1.getHealth(); // 200- assigned in constructor
monster1.getHealth(); // 100- inherited from the prototype object
|
Assigning a parent class to a child class requires that you call
new and assign the result to the child class'
prototype property, as shown in Listing 3. Therefore, it's
advisable to keep constructors as lean and free of side effects as
possible, unless you're willing to pass default values in your class
definitions.
If you've started trying to define classes and inheritance in JavaScript,
you might have realized a major difference from classical OOP languages:
there are no super or
parent properties to access a parent object's
methods if you've overridden them. There's a simple solution to this, but
it violates the Don't Repeat Yourself (DRY) principles and is probably the
biggest reason there are so many libraries that try to simulate classical
inheritance.
Listing 3. Calling parent methods from child classes
function ParentClass() {
this.color = 'red';
this.shape = 'square';
}
function ChildClass() {
ParentClass.call(this); // use 'call' or 'apply' and pass in the child
// class's context
this.shape = 'circle';
}
ChildClass.prototype = new ParentClass(); // ChildClass inherits from ParentClass
ChildClass.prototype.getColor = function() {
return this.color; // returns "red" from the inherited property
};
|
In Listing 3, the
color and shape
attribute values aren't in the prototype—they are assigned in the
ParentClass constructor function. New instances
of ChildClass will have their shape property
assigned twice—once as "squre" in the
ParentClass constructor, and once as "circle"
in the ChildClass constructor. Moving logic
such as these assignments to the prototype will reduce such side effects
and make the code easier to maintain.
In prototypical-inheritance models, you can use JavaScript's
call or apply
methods to run a function with a different context. Although this works
well to replace other languages' use of super
or parent, it creates a different problem. If
you need to refactor a class by changing its name, its parent, or its
parent's name, you now have the token
ParentClass in your text file in many more
places. This problem grows as your classes become more complex. A better
solution is to have your classes extend a base class, which allows your
code to repeat itself less, typically in the form of recreating classical
inheritance.
Though prototypical inheritance is perfectly capable for OOP, it does not meet a number of the goals of good programming. Consider the following issues:
- It isn't DRY. Class names and prototypes are repeated everywhere, making reading and refactoring more difficult.
- Constructors are called during prototyping. You're unable to use certain logic in constructors once you start subclassing.
- There's no real support for strong encapsulation.
- There's no real support for static class members.
Many JavaScript libraries attempt to impose more classical OOP syntax to overcome the issues above. One of the easier-to-use libraries is Dean Edward's Base.js (see Resources), which provides the following useful features:
- All prototyping is done with object mix-ins (classes and subclasses can be defined in one statement).
- A special constructor function is used to provide a safe place for logic to be run when creating new instances of the class.
- It provides static class member support.
- Its contribution towards strong encapsulation stops at keeping your class definition down to one statement (mental encapsulation, not code encapsulation).
While other libraries may offer stricter support for public and private methods and properties (encapsulation), Base.js provides a concise syntax that's easy to use and remember.
Listing 4 gives a brief
introduction to Base.js and classical inheritance. The example extends the
features of an abstract Enemy class with a more
specific RobotEnemy class.
Listing 4. Brief introduction to Base.js and classical inheritance
// create an abstract, basic class for all enemies
// the object used in the .extend() method is the prototype
var Enemy = Base.extend({
health: 0,
damage: 0,
isEnemy: true,
constructor: function() {
// this is called every time you use "new"
},
attack: function(player) {
player.hit(this.damage); // "this" is your enemy!
}
});
// create a robot class that uses Enemy as its parent
//
var RobotEnemy = Enemy.extend({
health: 100,
damage: 10,
// because a constructor isn't listed here,
// Base.js automatically uses the Enemy constructor for us
attack: function(player) {
// you can call methods from the parent class using this.base
// by not having to refer to the parent class
// or use call / apply, refactoring is easier
// in this example, the player will be hit
this.base(player);
// even though you used the parent class's "attack"
// method, you can still have logic specific to your robot class
this.health += 10;
}
});
|
The basic game engine invariably relies on two functions:
update and render.
The render method will typically rely on either
setInterval or a polyfill for
requestAnimationFrame, such as the one by Paul
Irish (see Resources). The benefit of using
requestAnimationFrame is that it won't be
called any more than necessary. It will only run at the client monitor's
refresh rate (typically 60 times a second for desktops), and, in most
browsers, it won't run at all unless the tab your game is in is active.
Benefits include:
- Reducing the amount of work the client computer does when the user isn't looking at the game.
- Saving battery life on mobile devices.
- Effectively pausing the game if your update loop is tied to the render loop.
For these reasons, requestAnimationFrame has
been called "client-friendly" and a "better citizen" in comparison to
setInterval.
Tying the update loop to the
render loop poses a different problem: keeping
the pace of the game actions and animations the same regardless of whether
the render loop is running at 15 or 60 frames per second. The trick is to
establish a unit of time, called a tick, within your game and
pass the amount of time elapsed since the last call to update. This amount
of time can then be translated into a number of ticks, and your models,
physics engines, and other time-dependent game logic can adjust
accordingly. For example, a player who is poisoned may take 10 damage per
tick for 10 ticks. If the render loop is running fast, they may take no
damage on a particular call to update. But, if garbage collection kicked
in on the last render loop causing one-and-a-half ticks to pass, your
logic may cause 15 damage instead.
Another school of thought is to completely decouple the timing of your
model updates from the view render loop. In games that have many
animations or objects, or are otherwise highly resource-intensive to draw,
coupling the update loop to the render loop
will cause the game to slow down completely. In this case, your
update method can run on a set interval (using
setInterval) regardless of when and how often
the requestAnimationFrame handler fires. Most
of the time spent in these loops is actually spent in the render step, so
the game will continue to run at a set pace even if only 25 frames are
drawn to the screen. In both cases, you'll still want to calculate a
difference in time between update cycles; if you update 60 times a second,
your update function has at most 16ms to finish. If it runs longer (or if
the browser's garbage collection runs) the game will still slow down.
Listing 5 shows an
example.
Listing 5. Basic application class with
render and update loops
// requestAnim shim layer by Paul Irish
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
var Engine = Base.extend({
stateMachine: null, // state machine that handles state transitions
viewStack: null, // array collection of view layers,
// perhaps including sub-view classes
entities: null, // array collection of active entities within the system
// characters,
constructor: function() {
this.viewStack = []; // don't forget that arrays shouldn't be prototype
// properties as they're copied by reference
this.entities = [];
// set up your state machine here, along with the current state
// this will be expanded upon in the next section
// start rendering your views
this.render();
// start updating any entities that may exist
setInterval(this.update.bind(this), Engine.UPDATE_INTERVAL);
},
render: function() {
requestAnimFrame(this.render.bind(this));
for (var i = 0, len = this.viewStack.length; i < len; i++) {
// delegate rendering logic to each view layer
(this.viewStack[i]).render();
}
},
update: function() {
for (var i = 0, len = this.entities.length; i < len; i++) {
// delegate update logic to each entity
(this.entities[i]).update();
}
}
},
// Syntax for Class "Static" properties in Base.js. Pass in as an optional
// second argument to.extend()
{
UPDATE_INTERVAL: 1000 / 16
});
|
In case you're not familiar with the context of
this in JavaScript, note that
.bind(this) is used twice—once on the
anonymous function inside the setInterval call,
and once on this.render.bind() inside the
requestAnimFrame call.
setInterval and
requestAnimFrame are functions, not methods;
they belong to the global window object and not to any particular class or
identity. As a result, in order for this inside
of the engine's render and update methods to reference our instance of the
Engine class, calling
.bind(object) will force
this inside of the function to act differently
than it would normally. If you're supporting Internet Explorer 8 or
earlier, you'll need to add a polyfill for bind.
The state machine pattern is widely implemented but poorly recognized. It is an extension of the principles behind OOP (abstract the concept of the code from the execution). For example, a game may have the following states:
- Preloading
- Start screen
- Active game
- Options menu
- Game over (win, lose, or continue)
None of these states should have executable code that is concerned about the other states. Your preloading code shouldn't know anything about when to open the Options menu. Imperative (procedural) programming might suggest monolithic if or switch conditional statements to properly order application logic, but they fail to represent the concept of the code, making them more difficult to maintain. Adding additional states, such as in-game menus, transitions between levels, or more features, makes your conditional statements harder to maintain.
Instead, consider the example in Listing 6.
Listing 6. Simplified state machine
// State Machine
var StateMachine = Base.extend({
states: null, // this will be an array, but avoid arrays on prototypes.
// as they're shared across all instances!
currentState: null, // may or may not be set in constructor
constructor: function(options) {
options = options || {}; // optionally include states or contextual awareness
this.currentState = null;
this.states = {};
if (options.states) {
this.states = options.states;
}
if (options.currentState) {
this.transition(options.currentState);
}
},
addState: function(name, stateInstance) {
this.states[name] = stateInstance;
},
// This is the most important function—it allows programmatically driven
// changes in state, such as calling myStateMachine.transition("gameOver")
transition: function(nextState) {
if (this.currentState) {
// leave the current state—transition out, unload assets, views, so on
this.currentState.onLeave();
}
// change the reference to the desired state
this.currentState = this.states[nextState];
// enter the new state, swap in views,
// setup event handlers, animated transitions
this.currentState.onEnter();
}
});
// Abstract single state
var State = Base.extend({
name: '', // unique identifier used for transitions
context: null, // state identity context- determining state transition logic
constructor: function(context) {
this.context = context;
},
onEnter: function() {
// abstract
// use for transition effects
},
onLeave: function() {
// abstract
// use for transition effects and/or
// memory management- call a destructor method to clean up object
// references that the garbage collector might not think are ready,
// such as cyclical references between objects and arrays that
// contain the objects
}
});
|
You might not need to create a specific subclass of the state machine for
your application, but you'll definitely want to create subclasses of
State for each of your application states. By
separating your transition logic into different objects, you could:
- Use the constructors as an opportunity to start immediately preloading assets.
- Add new states to your game, such as a continue screen that appears before a game over screen, without trying to figure out which global variables are affected in which conditionals in some monolithic if/else or switch structure.
- Dynamically define transition logic if you create states based on data loaded from a server.
Your main application class shouldn't be concerned about the logic inside of your states, and your states shouldn't be too concerned about anything inside of the main application class. A preloading state, for example, might be responsible for instantiating a view based on assets built into the markup of the page and queueing the minimum game assets (movie clips, images, and sounds) in a singleton asset manager. Although the state instantiated the preloading view class, it doesn't need to be concerned with the behavior of the view. In this case, the idea (the object that the state represents) is limited in responsibility to defining what it means to the application to be in a state of preloading data.
Keep in mind that the state machine pattern isn't limited to the states of your game logic. Individual views will also benefit by removing the state logic from their representation logic, especially when managing subviews or when combined with the Chain of Responsibility pattern to handle user interaction events.
Chain of Responsibility: Emulating event bubbling on a canvas
Think of the HTML5 canvas element as an image
element that lets you manipulate individual pixels. If you have an area
where you've drawn grass, some loot, and a character standing on top of it
all, the canvas doesn't have a clue what the user has clicked on. If you
draw a menu, the canvas doesn't know that a particular area represents a
button, and the only DOM element to attach an event to is the canvas
itself. To make a game playable, the game engine needs to be able to
translate what can happen when a user clicks on the canvas.
The Chain of Responsibility design pattern is intended to decouple the sender (the DOM element) of an event from the receiver (your code) so that more than one object has a chance to claim responsibility for handling the event (your views and models). Classical implementations, such as web pages, may have your views or models implement a handler interface then delegate all mouse events to a scene graph, which would find the relevant "things" that were clicked on and let each one have a chance at intercepting. A simpler approach is to simply have the canvas itself host a chain of handlers defined at runtime, as shown in Listing 7.
Listing 7. Handing event bubbling using the Chain of Responsibility pattern
var ChainOfResponsibility = Base.extend({
context: null, // relevant context- view, application state, so on
handlers: null, // array of responsibility handlers
canPropagate: true, // whether or not
constructor: function(context, arrHandlers) {
this.context = context;
if (arrHandlers) {
this.handlers = arrHandlers;
} else {
this.handlers = [];
}
},
execute: function(data) {
for (var i = 0, len = this.handlers.length; i < len; i++) {
if (this.canPropagate) {
// give a handler a chance to claim responsibility
(this.handlers[i]).execute(this, data);
} else {
// an event has claimed responsibility, no need to continue
break;
}
}
// reset state after event has been handled
this.canPropagate = true;
},
// this is the method a handler can call to claim responsibility
// and prevent other handlers from acting on the event
stopPropagation: function() {
this.canPropagate = false;
},
addHandler: function(handler) {
this.handlers.push(handler);
}
});
var ResponsibilityHandler = Base.extend({
execute: function(chain, data) {
// use chain to call chain.stopPropegation() if this handler claims
// responsibility, or to get access to the chain's context member property
// if this event handler doesn't need to claim responsibility, simply
// return; and the next handler will execute
}
});
|
The ChainOfResponsibility class will work just
fine without subclassing because all of your application-specific logic
will be contained inside of the subclasses of
ResponsibilityHandler. The only thing that
would change between implementations would be passing in an appropriate
context, such as the view that it's representing. Consider an options menu
that, while open, still shows the paused game around it, as shown in Listing 8. If the user clicks
on one of the buttons in the menu, the characters in the background
shouldn't react to the click.
Listing 8. Options menu close handler
var OptionsMenuCloseHandler = ResponsibilityHandler.extend({
execute: function(chain, eventData) {
if (chain.context.isPointInBackground(eventData)) {
// the user clicked the transparent background of our menu
chain.context.close(); // delegate changing state to the view
chain.stopPropegation(); // the view has closed, the event has been handled
}
}
});
// OptionMenuState
// Our main view class has its own states, each of which handles
// which chains of responsibility are active at any time as well
// as visual transitions
// Class definition...
constructor: function() {
// ...
this.chain = new ChainOfResponsibility(
this.optionsMenuView, // the chain's context for handling responsibility
[
new OptionsMenuCloseHandler(), // concrete implementation of
// a ResponsibilityHandler
// ...other responsibility handlers...
]
);
}
// ...
onEnter: function() {
// change the view's chain of responsibility
// guarantees only the relevant code can execute
// other states will have different chains to handle clicks on the same view
this.context.setClickHandlerChain(this.chain);
}
// ...
|
In Listing 8, a
view class has a reference to a set of states,
and each state determines which objects will be responsible for how a
click event is handled. In this way, the view's logic is limited to only
what the view's identity represents: displaying the options menu. If the
game is updated to include more buttons, fancier effects, or transitions
to new views, there is a discrete object capable of handling each new
feature without any need to change, break, or rewrite the existing logic.
Through a clever combination of chains for mousedown, mousemove, mouseup,
and click events, managing everything from menus to characters, to drag
and drop inventory screens can be handled in a highly structured and
organized manner without increasing the complexity of your code.
Design patterns and OOP are inherently neutral concepts; blind usage of them can cause problems, instead of solving problems. This article provided an overview of OOP in JavaScript and explored the prototypical and classical inheritance models. You learned about common patterns in games that can greatly benefit from the structure and maintainability of OOP design (a basic game loop, state machine, and event bubbling). This article has only skimmed the surface of common solutions to common problems. With a bit of practice, you should become adept at writing expressive code—and consequently spend less time writing and more time creating.
| Description | Name | Size | Download method |
|---|---|---|---|
| Article source code | Object-OrientedDesignSource.zip | 5KB | HTTP |
Information about download methods
Learn
-
A Base Class for
JavaScript Inheritance (Dean Edwards, March 2006): Learn more from
this blog about Dean's JavaScript class that helps ease the pain of
JavaScript OO.
-
requestAnimationFrame for smart animating: Read about this basic
API for use with animation on Paul Irish's website.
-
requestAnimationFrame for smart(er) animating: Read Erik Moller's
blog for another approach to
requestAnimationFrame. -
Design Patterns: Elements of Reusable Object-Oriented Software
(Addison-Wesley Professional Computing Series, 1995): Go through this
catalog of simple and succinct solutions to commonly occurring design
problems and expressive code.
- "Create great graphics with the HTML5 canvas" (developerWorks,
February 2011): Learn to enhance your web pages with Canvas, a simple
HTML5 element that packs a punch.
-
HTML5 Canvas: View this demo that focuses on the use of the
canvas API and shows you how to paint a very simple animation.
- "HTML5 fundamentals, Part 4: The final touch: The Canvas"
(developerWorks, July 2011): Read this introduction to the HTML5 canvas
element. Several examples to demonstrate functions are included.
-
Canvas
Pixel Manipulation: Look at this demo, an excellent example of
managing the canvas to develop effective visual assets, from the Safari
Dev Center.
-
jQuery Events API:
Learn about methods used to register behaviors to take effect when the
user interacts with the browser.
-
WHATWG: Explore this community of
developers working with the W3C to fine-tune HTML5.
-
Canvas
tutorial: Check out this tutorial and demo from the Mozilla
developers.
-
HTML5 Canvas
reference: Explore W3Schools.com's useful exercises to help hone
your canvas knowledge.
- developerWorks Web
development zone: Find articles covering various web-based
solutions. See the Web
development technical library for a wide range of technical
articles and tips, tutorials, standards, and IBM Redbooks.
- developerWorks
technical events and webcasts: Stay current with technology in
these sessions.
- developerWorks Live! briefings: Get up to speed quickly on IBM
products and tools as well as IT industry trends.
-
developerWorks on-demand demos: Watch demos ranging from product
installation and setup for beginners, to advanced functionality for
experienced developers.
- developerWorks on
Twitter: Join today to follow developerWorks tweets.
Get products and technologies
-
OpenGL: Get the latest drivers.
-
jQuery: Download the popular JavaScript
Library that simplifies HTML document traversing, event handling,
animating, and Ajax interactions for rapid web development.
-
Modernizr: Get this open-source
JavaScript library that helps you build the next generation of HTML5 and
CSS3-powered websites.
-
Kibo: Download the popular
library specifically written for speedy cross-browser keyboard event
handling.
-
IBM product
evaluation versions: Download or explore
the online trials in the IBM SOA Sandbox and get your hands on
application development tools and middleware products from DB2, Lotus,
Rational, Tivoli, and WebSphere.
Discuss
- developerWorks
community: Connect with other developerWorks users while exploring
the developer-driven blogs, forums, groups, and wikis.
- Find other developerWorks members interested in web development.

Dan Zdrazil began working in PHP in 2008, then added Flex/AS3, Zend, JavaScript, and HTML to his portfolio. He works full-time as a software engineer at The Nerdery and frequently shares his expertise by participating in the company's JavaScript committee for those passionate about the technology. Zdrazil is a graduate of the University of Minnesota with a Bachelor of Arts in Asian language and literature.



