27 February 2012
Beginning
In this article, I'm going to discuss object creation in JavaScript using prototypal inheritance as an alternative to the new operator.
One significant aspect of JavaScript is that there is rarely a single right way to do any particular task. JavaScript is a loosely-typed, dynamic, and expressive language, which means that there are usually many different ways to accomplish the same task. I'm not saying that the methods described here to create objects are the only correct ways to do so or even the best ways, but I do feel that they are closer to the true nature of the language and will help you to understand what's going on under the covers if you choose to use other methods.
To help you better understand these concepts, this article describes the creation of a basic particle system with multiple rendering targets. This is a complex enough task to represent a real world test of the concepts I'll be demonstrating, rather than a simple hello world type of application.
The crux of this article is the creation of JavaScript objects. Most tutorials you see will tell you to create a constructor function, add methods to the function's prototype property, and then use the new operator like so:
function Foo() {
this.name = "foo";
}
Foo.prototype.sayHello = function() {
alert("hello from " + this.name);
};
var myFoo = new Foo();
myFoo.sayHello();
The newly created object now has all the properties that were defined on the constructor function's prototype. This creates something that looks much like a class in a class-based language. To make a new "subclass" that inherits from that "class", you'd set the prototype property of the "subclass" to a new instance of the original "class". (I'm using quotation marks here because the entities are not actual classes or subclasses.)
function Bar() {
}
Bar.prototype = new Foo();
Bar.prototype.sayGoodbye = function() {
alert("goodbye from " + this.name);
}
var myBar = new Bar();
myBar.sayHello();
myBar.sayGoodbye();
The problem is that because this structure looks so similar to real classes in other languages, people start expecting it to behave exactly like real classes behave in other languages. But the more you work with these types of "classes", the more you see that they don't behave that way at all. So people get upset with JavaScript, and start thinking it is bad language that can't be used for anything serious. Others go about trying to fix these class-like structures, tacking on various bits of functionality and building up very complex frameworks to get constructor functions and prototypes to look and behave more and more like classes.
Personally, I see this as a bit of a misguided effort. It's not necessarily wrong, but the energy being spent would likely produce better results if it was in another direction.
JavaScript is not a class-based language, but a prototype-based one. Code reuse is done not by making class templates that are used to instantiate objects, but by creating new objects directly, and then making other new objects based on existing ones. The existing object is assigned as the prototype of the new object and then new behavior can be added to the new object. It's quite an elegant system and it's beautifully implemented in the Io language, which I encourage you to look into.
Before going any further, I want to clarify the term prototype. First, there is the prototype property of a constructor function as shown in the last section's example. There is another hidden property that is the actual prototype of an object. This can be very confusing. The ECMAScript proposal refers to this hidden property as [[Prototype]] . This is exposed in some JavaScript environments as the __proto__ property, but this is not a standard part of the language and should not be counted on. When you create a new object using new with a constructor function, that new object's [[Prototype]] is set with a reference to the constructor function's prototype.
In addition to this naming confusion, there were two design decisions made in the language that have added to the confusion ever since. First, due to the concern that some developers might not be comfortable with prototypal inheritance, constructor functions and the new operator were introduced. Second, there was no direct native way to create a new object with another object as its [[Prototype]] , except through the new operator with a constructor function.
Fortunately, most browsers now support the Object.create method. This method takes an existing object as a parameter. It returns a new object that has the existing object assigned as its [[Prototype]] . Even more fortunately, this method is quite easy to create for those environments that do not support it:
if(typeof Object.create !== "function") {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
So, how would you rewrite the earlier example using Object.create ? First you create a foo object that has a name property and a sayHello function:
var foo = {
name: "foo",
sayHello: function() {
alert("hello from " + this.name);
}
};
foo.sayHello();
Then, you use Object.create to make a bar object that has foo as its prototype, and add a sayGoodbye function to it:
var bar = Object.create(foo);
bar.sayGoodbye = function() {
alert("goodbye from " + this.name);
}
bar.sayHello();
bar.sayGoodbye();
It's also very common to create an extend function that simplifies the adding of methods and properties to the new object. The following method simply copies over any properties from props onto obj :
function extend(obj, props) {
for(prop in props) {
if(props.hasOwnProperty(prop)) {
obj[prop] = props[prop];
}
}
}
This enables you to create bar like so:
var bar = Object.create(foo);
extend(bar, {
sayGoodbye: function() {
alert("goodbye from " + this.name);
}
});
Not such a big deal here, but it simplifies things greatly when you are adding several more properties or methods.
OK, now that you have the basics down, you can start putting them together in a real world scenario.
The particles used in the example project are going to be very basic: black dots that move around in a two-dimensional space and bounce off the walls. They also support gravity and friction as needed. You'll define a particle object that has all the properties and methods it needs, and place it in an adc object to avoid polluting the global namespace. For more information on namespaces, see the Wikipedia article on unobtrusive Javascript.
var adc = adc || {};
adc.particle = {
x: 0,
y: 0,
vx: 0,
vy: 0,
gravity: 0.0,
bounce: -0.9,
friction: 1.0,
bounds: null,
color: "#000000",
context: null,
update: function() {
this.vy += this.gravity;
this.x += this.vx;
this.y += this.vy;
this.vx *= this.friction;
this.vy *= this.friction;
if(this.x < this.bounds.x1) {
this.x = this.bounds.x1;
this.vx *= this.bounce;
}
else if(this.x > this.bounds.x2) {
this.x = this.bounds.x2;
this.vx *= this.bounce;
}
if(this.y < this.bounds.y1) {
this.y = this.bounds.y1;
this.vy *= this.bounce;
}
else if(this.y > this.bounds.y2) {
this.y = this.bounds.y2;
this.vy *= this.bounce;
}
},
render: function() {
if(this.context === null) {
throw new Error("context needs to be set on particle");
}
this.context.fillStyle = this.color;
this.context.fillRect(this.x - 1.5, this.y - 1.5, 3, 3);
}
};
Next, you'll need a particle system to keep track of all the particles and handle updating and rendering them.
var adc = adc || {};
adc.particleSystem = {
particles: [],
addParticle: function(particle) {
this.particles.push(particle);
},
update: function() {
var i,
numParticles = this.particles.length;
for(i = 0; i < numParticles; i += 1) {
this.particles[i].update();
}
},
render: function() {
var i,
numParticles = this.particles.length;
for(i = 0; i < numParticles; i += 1) {
this.particles[i].render();
}
}
};
And finally, you'll need a main file that creates the system, creates and adds all the particles, and sets up the animation loop.
(function() {
if (typeof Object.create !== "function") {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
var system,
numParticles,
canvas,
context,
bounds;
function initSystem() {
system = Object.create(adc.particleSystem);
numParticles = 200;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
bounds = {
x1: 0,
y1: 0,
x2: canvas.width,
y2: canvas.height
};
}
function initParticles() {
var i, particle;
for(i = 0; i < numParticles; i += 1) {
particle = Object.create(adc.particle);
particle.bounds = bounds;
particle.context = context;
particle.x = Math.random() * bounds.x2;
particle.y = Math.random() * bounds.y2;
particle.vx = Math.random() * 10 - 5;
particle.vy = Math.random() * 10 - 5;
system.addParticle(particle);
}
}
function animate() {
context.clearRect(bounds.x1, bounds.y1, bounds.x2, bounds.y2);
system.update();
system.render();
}
initSystem();
initParticles();
setInterval(animate, 1000 / 60);
}());
The code in this file is contained in an immediately invoked function expression, again to avoid global namespace pollution. (For more information on immediately invoked function expressions, see Ben Alman's blog about the topic. It includes the Object.create shim for browsers that might need it. This is all pulled together in the following HTML file:
<!DOCTYPE html>
<html>
<head>
<title>Particles v1</title>
<style type="text/css">
.html, body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div>
<canvas id="canvas"/>
</div>
<script type="text/javascript" src="v1/particle.js"></script>
<script type="text/javascript" src="v1/particleSystem.js"></script>
<script type="text/javascript" src="v1/main.js"></script>
</body>
</html>
The important lines, for the purposes of this article, are those that create the particle system:
system = Object.create(adc.particleSystem);
and that create the particles themselves:
particle = Object.create(adc.particle);
particle.bounds = bounds;
particle.context = context;
particle.x = Math.random() * bounds.x2;
particle.y = Math.random() * bounds.y2;
particle.vx = Math.random() * 10 - 5;
particle.vy = Math.random() * 10 - 5;
You haven't implemented any kind of extend function yet, but you can see here where it would be useful – calling extend a single time, rather than line after line of assigning properties. In the next iteration, you'll add that and then some.
For the second version of the particle system, rather than having the main file create and extend each and every particle by itself, it would be better to have the particles know how to create, extend, and initialize themselves. To support that, you can use two new functions, extend and init , which are added to adc.particle :
var adc = adc || {};
adc.particle = {
x: 0,
y: 0,
vx: 0,
vy: 0,
gravity: 0.0,
bounce: -0.9,
friction: 1.0,
bounds: null,
color: "#000000",
context: null,
extend: function(props) {
var prop, obj;
obj = Object.create(this);
for(prop in props) {
if(props.hasOwnProperty(prop)) {
obj[prop] = props[prop];
}
}
return obj;
},
init: function() {
this.x = Math.random() * this.bounds.x2;
this.y = Math.random() * this.bounds.y2;
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
},
// …
// rest of methods are the same as version 1
};
The extend method takes care of creating a new object, passing this as a parameter to Object.create . Thus, it makes a copy of itself. It then takes any properties that were passed into extend , copies them onto the new object it created, and finally returns the new object.
Now, rather than calling Object.create(adc.particle) and setting and tweaking property after property, you can call adc.particle.extend , passing in an object with the properties you want to set, and then call init on the newly created particle.
When you add the extend method to the particle system, the main file becomes a bit simpler. In initSystem , you call adc.particleSystem.extend() to create the new system. You don't need to add any properties to the system, so extend is called with no parameters. Not much of a change there:
function initSystem() {
system = adc.particleSystem.extend();
numParticles = 200;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
bounds = {
x1: 0,
y1: 0,
x2: canvas.width,
y2: canvas.height
};
}
In the initParticles method, though, you see an improvement:
function initParticles() {
var i, particle;
for(i = 0; i < numParticles; i += 1) {
particle = adc.particle.extend({
bounds: bounds,
context: context
});
particle.init();
system.addParticle(particle);
}
}
Now you can call adc.particle.extend to create each particle, passing in an object that contains the bounds and context , which are then copied to each particle. Finally, you just call init on the new particle, which takes care of randomly setting up its position and velocity. This version works exactly the same as the last, but the creation of individual particles has been greatly simplified.
The third version of the particle system supports inheritance. This is key to code reuse. You have one type of object and you want to make another type of object that is slightly different. You don't want to completely recreate the first object with just a couple of changes.
Code reuse has two important benefits. First, there is less code to write. You certainly don't want to write the same code twice. You also don't want to copy and paste code, as this can lead to things getting out of sync, with a function implemented one way over here and the same function implemented a bit differently over there. The second benefit is better performance. When you have the same code duplicated in your live application, it takes longer to download, eats up more memory, and can cause your code to be slower, particularly in object instantiation (because it is instantiating the same code again and again).
The particle system currently renders to an HTML5 canvas. Now, you may want to make a different particle type that renders itself as a DOM object. Ideally, almost all of the particle code would be reused, with only the render method differing. So, with great confidence, you can just take particle.js and remove the render method from it.
Next, make two new files, canvasParticle.js and comParticle.js. The canvas version will be similar to what you've just done:
var adc = adc || {};
adc.canvasParticle = adc.particle.extend({
render: function() {
if(this.context === null) {
throw new Error("context needs to be set on particle");
}
this.context.fillStyle = this.color;
this.context.fillRect(this.x - 1.5, this.y - 1.5, 3, 3);
}
});
This code is quite simple. You just call adc.particle.extend , passing in an object that contains your old render method. This will create a new object that has particle as its [[Prototype]] , and render as a new method directly on the object.
Next you'll have to change main.js a bit to allow for your new object types. Create a mainCanvas.js file for setting up the canvas-based particles. It will only differ in one line, where it uses the adc.canvasParticle type to instantiate particles, rather than just adc.particle :
function initParticles() {
var i, particle;
for(i = 0; i < numParticles; i += 1) {
particle = adc.canvasParticle.extend({
bounds: bounds,
context: context
});
particle.init();
system.addParticle(particle);
}
}
The particleSystem.js file can remain unchanged, but of course the HTML file will have to reflect new source files you've created. This example should function identically to the first two versions.
Now you're ready to create the DOM version
The domParticle.js file will be almost as simple as canvasParticle.js. It assumes that there is an element it can position, and positions it using style properties:
adc.domParticle = adc.particle.extend({
render: function() {
if(this.element === null) {
throw new Error("element needs to be set on particle");
}
this.element.style.left = this.x;
this.element.style.top = this.y;
}
});
But in this example, the HTML file and main.js file will need to change significantly. In addition to referencing different source files, the HTML file can eliminate the canvas element and add a container div in which to put all the particle elements:
<html>
<head>
<title>Particles v3</title>
<style type="text/css">
.html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container">
</div>
<script type="text/javascript" src="v3/particle.js"></script>
<script type="text/javascript" src="v3/domParticle.js"></script>
<script type="text/javascript" src="v3/particleSystem.js"></script>
<script type="text/javascript" src="v3/mainDom.js"></script>
</body>
</html>
The main.js file will become mainDom.js and will obviously need to change a bit to create domParticles and give them individual elements instead of references to the canvas's context.
(function() {
var system,
numParticles,
container,
bounds;
function createElement() {
var el = document.createElement("div");
el.style.position = "absolute";
el.style.width = 3;
el.style.height = 3;
el.style.backgroundColor = "#000000";
container.appendChild(el);
return el;
}
function initSystem() {
system = adc.particleSystem.extend();
numParticles = 200;
container = document.getElementById("container");
bounds = {
x1: 0,
y1: 0,
x2: window.innerWidth,
y2: window.innerHeight
};
}
function initParticles() {
var i, particle;
for(i = 0; i < numParticles; i += 1) {
particle = adc.domParticle.extend({
bounds: bounds,
element: createElement()
});
particle.init();
system.addParticle(particle);
}
}
function animate() {
system.update();
system.render();
}
initSystem();
initParticles();
setInterval(animate, 1000 / 60);
}());
This makes use of a new function, createElement , that simply creates a div , styles it, and adds it to the container div . This is what the particle will position when its render method is called.
This final example should be nearly identical to all the other versions. Of course, there are lots of optimization and enhancements that you can do to improve all of these examples. I purposely kept it simple to better illustrate the inheritance aspect.
You may still prefer constructor functions and the new operator. I'm not going to twist your arm about it. I personally find this method of object creation to be very clean and in accord with the basic prototypal nature of JavaScript.
I encourage you to explore the source code provided in the sample files for this tutorial and try out the particle system in a browser that supports HTML5.
As your needs for more complex apps grow, you can add features onto this basic setup far more cleanly than you can with an pseudo-class based system. For more information, see the following resources:
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.