Object-Oriented JavaScript
In this tutorial, we’re going to explore the different ways you can write Object-Oriented JavaScript, the reasons for doing so, and a few basic implementations. Throughout the tutorial, you will be provided with JSFiddle examples, which illustrate how to create a script that will respond when the user clicks, create a new custom Object from a custom Class at that location, and animate that Object. Here’s a demo of what we’re working towards.
Step 1:
For example, examine the following JSFiddle:
http://jsfiddle.net/PrimordialSoup/GHZ6g/
In this example, the user clicks on the screen at varying locations, creating tiles there. The tiles for now simply display x
and y
values which are assigned based on where the user has clicked. If we wish to animate the tiles or interact with the user, we need to keep track of them, so we’ll create an array of tiles
. We will also create two more variables – x
and y
, which we’ll use to keep track of where the user is clicking.
[sourcecode language=”javascript” firstline=”4″ classname=”code-add”]
var x, y;
var tiles = [];
[/sourcecode]
In the onload
function, we add an event listener to the document to listen for mouse clicks, and we set up a timer to update the content area every second.
In the actionClick(e)
function, we grab the event’s x and y coordinates, create a new DOM node to represent the tile, and position it at that location. We also set it to display the current x and y values.
In the updateContent
function, we iterate through all the tiles and update them to display the current x and y values. However, since these variables are declared in the global scope, they get updated to the latest coordinates where the user has clicked. You will notice, then, that when you first click to place a tile on the screen, it reads its own coordinates, but once the timer triggers to update all the tiles with the global values, all the rest of the tiles take on that value as well.
This creates a problem in our case, as we wish for each tile to keep track of its own x and y coordinates. So, instead of creating arrays of x and y coordinates (or any other values we may want to keep track of) we will use Objects, and let them keep track of their own values. This way, all you have to keep track of is your objects, and let them do all the work that’s specific to them.
Step 2:
Consider this next JSFiddle:
http://jsfiddle.net/PrimordialSoup/JVmwv/
In this example we got rid of our global x
and y
variables, and instead created a Tile Class
. When we instantiate (create an instance of) the Tile Class
, we’ll create a Tile Object
, which will keep track of it’s own x
, y
and node
values. The node
variable will point to the DOM Node for that tile.
[sourcecode language=”javascript” firstline=”4″ classname=”code-remove”]
var x, y;
[/sourcecode]
[sourcecode language=”javascript” firstline=”10″ classname=”code-add”]
function Tile() {
this.x = 0;
this.y = 0;
this.node = null;
}
[/sourcecode]
This is where first introduce Object Oriented Java Script (OOJS). We create the Tile Class
by writing function Tile() {...}
. The Tile
acts as a function in that it is a piece of code that can be executed when it is called by name.
As a refresher on Object Oriented Programming, recall that a Class
is a construct which defines our object model. It does not exist as an actual object just yet – it is only a template of what our objects will look like. In our example, we have defined that the Tile Object
will have an x
, y
and node
properties.
We will be creating new instances of our Tile Class
by writing var foo = new Tile();
. We assign properties or attributes to instances of our class by simply declaring variables in the class’ local scope using the this
keyword to reference that particular instance, or from outside the object by writing foo.x = 0;
.
Next in our code we updated the actionClick(e)
function by first making sure we use local x
and y
variables rather than global ones.
[sourcecode language=”javascript” firstline=”30″ classname=”code-remove-no-margin”]
x = e.pageX;
y = e.pageY;
[/sourcecode]
[sourcecode language=”javascript” firstline=”41″ classname=”code-add”]
var x = e.pageX;
var y = e.pageY;
[/sourcecode]
Then we create a Tile Object
by instantiating the Tile Class
. We assign to that specific tile it’s x and y coordinates, and the DOM Node, and then add that Tile Object
to our tiles
array. Similarly to how we were already keeping track of tiles, we will use this array to keep track of Objects rather than DOM Nodes.
var newTileObject = new Tile();
newTileObject.x = x;
newTileObject.y = y;
newTileObject.node = newTile;
[/sourcecode]
[sourcecode language=”javascript” firstline=”58″ classname=”code-add”]
tiles.push( newTileObject );
[/sourcecode]
In the updateContent
function, now that we are iterating through Objects rather than DOM Nodes, we updated the code to change the innerHTML
of the DOM Node, and to use the Object’s x and y coordinates. To make sure that it’s working, we also added a bit of code that adds “[n]” to each tile, so we can see that they are being updated, and being updated with correct coordinates.
[sourcecode language=”javascript” firstline=”52″ classname=”code-remove-no-margin”]
var obj = tiles[tile];
[/sourcecode]
[sourcecode language=”javascript” firstline=”68″ classname=”code-add”]
var obj = tiles[tile].node;
[/sourcecode]
[sourcecode language=”javascript” firstline=”54″ classname=”code-remove-no-margin”]
obj.innerHTML = ‘(‘+x+’×’+y+’)’;
[/sourcecode]
[sourcecode language=”javascript” firstline=”70″ classname=”code-add”]
obj.innerHTML = ‘(‘+tiles[tile].x+’×’+tiles[tile].y+’)’;
obj.innerHTML += ‘ <span class="small">[‘ + tile + ‘]</span>’;
[/sourcecode]
In this step we learned how to define a JavaScript Class and how to instantiate JavaScript Objects using the new
keyword. In the next step we introduce a different notation and a different way to work with Objects.
Step 3:
http://jsfiddle.net/PrimordialSoup/CcYfC/
In this step we introduce a PS
variable in the global scope. In order to make sure all of our variables are kept unique (in case another script also uses the same variable names). We declare it as an object by adding this line to the top of the file:
[sourcecode language=”javascript” firstline=”4″ classname=”code-add”]
var PS = {};
[/sourcecode]
This is similar to declaring a variable with an integer or a string value, except here we are defining our variable as an object. We can then put our tiles
array inside this object:
[sourcecode language=”javascript” firstline=”4″ classname=”code-remove-no-margin”]
var tiles = [];
[/sourcecode]
[sourcecode language=”javascript” firstline=”5″ classname=”code-add”]
PS.tiles = [];
[/sourcecode]
This allows us to keep things which are related to our code under this one object.
This notation of working with objects is no different from the notation in the previous step where we defined our Tile Class
, except we are skipping the part where we define our object model, and are assigning properties to our object on the fly.
Next, we update the onload
function to get the screen width and height, and record it in our global PS
object.
[sourcecode language=”javascript” firstline=”35″ classname=”code-add”]
var content = document.getElementById(‘content’);
PS.screenW = content.scrollWidth;
PS.screenH = content.scrollHeight;
[/sourcecode]
We then add a draw
function to our Tile Class
, which updates the tile’s text to display it’s current coordinates, as well as the screen width and height.
[sourcecode language=”javascript” firstline=”18″ classname=”code-add”]
this.draw = function() {
var obj = this.node;
// update x,y coordinates
obj.innerHTML = ‘(‘+this.x+’×’+this.y+’)’
+ ‘<br/>’
+ ‘<span class="small">’
+ ‘[‘+PS.screenW+’×’+PS.screenH+’]’
+ ‘</span>’;
}
[/sourcecode]
We also update the actionClick(e)
function to push our new tiles into the PS.tiles
array.
[sourcecode language=”javascript” firstline=”58″ classname=”code-remove-no-margin”]
tiles.push( newTileObject );
[/sourcecode]
[sourcecode language=”javascript” firstline=”73″ classname=”code-add”]
PS.tiles.push( newTileObject );
[/sourcecode]
Finally, we update the updateContent
function by moving the code from the loop into the draw
function of our Tile Class
, and simply calling the draw
function from the loop. We also made sure that we’re iterating through our array in PS.tiles
, rather than tiles
.
Step 4:
http://jsfiddle.net/PrimordialSoup/CsMDX/
In this step we add vX
and vY
variables to our Tile Class
, and a move
function, which uses the screen width and height we added in the previous step to determine the position of this tile in relation to the center of the screen. It then sets the Object’s vX
and vY
(v is for velocity) values to either a positive or a negative value, and updates the tile’s coordinates by adding that velocity to the x
and y
coordinates.
[sourcecode language=”javascript” firstline=”18″ classname=”code-add”]
this.vX = 0;
this.vY = 0;
[/sourcecode]
[sourcecode language=”javascript” firstline=”21″ classname=”code-add”]
this.move = function() {
// calculate offset from the center
var dX = this.x – ( PS.screenW / 2 );
var dY = this.y – ( PS.screenH / 2 );
this.vX = ( dX < 0 ) ? -1 : 1;
this.vY = ( dY < 0 ) ? -1 : 1;
// move away from the center
this.x = this.x + this.vX;
this.y = this.y + this.vY;
};
[/sourcecode]
The draw
method now also updates the node’s top
and left
style properties so that the DIV actually moves on the screen.
[sourcecode language=”javascript” firstline=”32″ classname=”no-margin”]
this.draw = function() {
…
[/sourcecode]
[sourcecode language=”javascript” firstline=”35″ classname=”code-add-no-margin”]
obj.style.left = this.x + ‘px’;
obj.style.top = this.y + ‘px’;
[/sourcecode]
[sourcecode language=”javascript” firstline=”37″]
…
}
[/sourcecode]
The updateContent
method has also been updated to call the move
function on each tile before re-drawing them.
[sourcecode language=”javascript” firstline=”96″ classname=”code-add”]
PS.tiles[tile].move();
[/sourcecode]
What we end up with are tiles that remember their own coordinates, have individual velocities away from the center, calculate and update their coordinates based on the velocity, and update the objects on the screen with the new information. We can now start finesseing and improving the visual elements of our script.
Step 5:
http://jsfiddle.net/PrimordialSoup/VXFJx/
In this step, we update the animation timer to execute at 30fps, instead of once every second. This creates a smooth animation for our tiles.
setInterval( updateContent , 1000 );
[/sourcecode]
[sourcecode language=”javascript” firstline=”56″ classname=”code-add”]
setInterval( updateContent , 33 );
[/sourcecode]
We also restructure the way our tiles get drawn in preparation for the next step. Instead of listening for the MouseClick
event, we set our event handlers to listen for MouseDown
and MouseUp
events.
addEvent( document , ‘click’ , actionClick );
[/sourcecode]
[sourcecode language=”javascript” firstline=”53″ classname=”code-add”]
addEvent( document , ‘mousedown’ , actionMouseDown );
addEvent( document , ‘mouseup’ , actionMouseUp );
[/sourcecode]
Each event executes an appropriate function – the actionMouseDown(e)
function sets a global PS.drawing
flag to true
, changes the border color of the content area to red, and fires the actionClick
function to create a new tile. The actionMouseUp(e)
function resets the PS.drawing
flag to false, and changes the border color back to blue.
[sourcecode language=”javascript” firstline=”6″ classname=”code-add”]
PS.drawing = false;
[/sourcecode]
[sourcecode language=”javascript” firstline=”63″ classname=”code-add”]
function actionMouseDown(e) {
// set the flag
PS.drawing = true;
// update the border color
var content = document.getElementById(‘content’);
content.style.borderColor = ‘red’;
// draw the tile
actionClick(e);
}
[/sourcecode]
[sourcecode language=”javascript” firstline=”77″ classname=”code-add”]
function actionMouseUp(e) {
// unset the flag
PS.drawing = false;
// update the border color
var content = document.getElementById(‘content’);
content.style.borderColor = ‘blue’;
}
[/sourcecode]
You’ll also notice that in our actionClick(e)
function, we are wrapping all the code in an if
statement to test whether the PS.drawing
flag is set to true
, this will come in handy in the next step.
[sourcecode language=”javascript” firstline=”90″ classname=”code-add-no-margin”]
if( PS.drawing == true ) {
[/sourcecode]
[sourcecode language=”javascript” firstline=”100″ classname=”code-add-no-margin”]
…
[/sourcecode]
[sourcecode language=”javascript” firstline=”115″ classname=”code-add”]
}
[/sourcecode]
Step 6:
http://jsfiddle.net/PrimordialSoup/VyZBr/
In this step, we added another event listener to listen for the MouseMove
event, and instead of creating our tiles when the MoueDown
event is detected, we create them when the MouseMove
event is detected.
[sourcecode language=”javascript” firstline=”55″ classname=”code-add”]
addEvent( document , ‘mousemove’ , actionMouseMove );
[/sourcecode]
[sourcecode language=”javascript” firstline=”88″ classname=”code-remove-no-margin”]
function actionClick(e) {
[/sourcecode]
[sourcecode language=”javascript” firstline=”88″ classname=”code-add”]
function actionMouseMove(e) {
[/sourcecode]
As you remember, from the previous step, we are only creating tiles when the PS.drawing
flag is set to true
, and that only happens when the MouseDown
event sets our PS.drawing
flag to true
. Once a MouseUp
event is detected, the flag is reset to false
, and the tiles are no longer drawn when the mouse moves.
Because of this code, you’ll see in this example that you can now click-and-drag to create our tiles, except they are no longer displaying their coordinates (as that is quite boring), but are now displaying a random character.
At the top of our code, we created an array of possible characters we wish to use, and called it PS.strings
.
[sourcecode language=”javascript” firstline=”7″ classname=”code-add”]
PS.strings = [‘♩’, ‘♪’, ‘♫’, ‘♬’, ‘♡’, ‘♥’];
[/sourcecode]
In our actionMouseMove(e)
function, instead of setting the innerHTML of our object to the x and y coordinates, we now generate a random number from zero to the length of our PS.strings
array, and use the value from the array at that index.
[sourcecode language=”javascript” firstline=”103″ classname=”code-remove-no-margin”]
newTile.innerHTML = ‘(‘+x+’×’+y+’)’;
[/sourcecode]
[sourcecode language=”javascript” firstline=”103″ classname=”code-add”]
var r = Math.floor( Math.random() * PS.strings.length );
newTile.innerHTML = PS.strings[r];
[/sourcecode]
We also removed the code from the draw
function which updated the innerHTML
of our node, as that is no longer necessary.
[sourcecode language=”javascript” firstline=”38″ classname=”code-remove”]
obj.innerHTML = ‘(‘+this.x+’×’+this.y+’)’;
[/sourcecode]
Step 7:
http://jsfiddle.net/PrimordialSoup/s68Vy/
In the previous example, you’ll notice that all of our tiles are very close together as they get drawn in the document. They also move at the same speed, so they stay in those clusters until they are gone. In order to get our tiles out of these clusters, we’ll assign a random speed to them, by creating some random values for vX
and vY
.
Because we don’t want to re-calcuate the velocity and the random speed multiplier every time we move the tile, we’ll create another function which performs this calculation once. We’ll call this function init
, and run it when the tile is first created.
[sourcecode language=”javascript” firstline=”120″ classname=”code-add”]
newTileObject.init();
[/sourcecode]
[sourcecode language=”javascript” firstline=”23″ classname=”code-add”]
this.init = function() {
// calculate offset from the center
var dX = this.x – ( PS.screenW / 2 );
var dY = this.y – ( PS.screenH / 2 );
// create random multipliers
var rX = Math.random() * 3;
var rY = Math.random() * 3;
this.vX = ( ( dX < 0 ) ? -1 : 1 ) * rX;
this.vY = ( ( dY < 0 ) ? -1 : 1 ) * rY;
}
[/sourcecode]
Our move
function gets simplified to only update the node’s location.
[sourcecode language=”javascript” firstline=”34″]
this.move = function() {
// move away from the center
this.x = this.x + this.vX;
this.y = this.y + this.vY;
};
[/sourcecode]
What we are left with is almost the code we set out to get in the beginning. There are just a few housekeeping things we need to take care of before we can consider this script complete.
Step 8:
http://jsfiddle.net/PrimordialSoup/B8U2b/
In our final step, we clean up our memory, and add the last finishing touches on the appearance of the tiles.
First, we make sure that we destroy the tiles after they leave the boundaries of the screen, because currently, they continue to float away forever. We’ll do this in our updateContent
function, while iterating through all the tiles, after calling the move
function. We’ll test whether the new coordinates are beyond the screen boundaries, and if so, we’ll remove the tile from the screen, and from the array, thus freeing up memory, and creating a smoother experience for the user.
In order to test whether the tiles left the screen boundaries, we’ll first need to calculate those values:
[sourcecode language=”javascript” firstline=”133″ classname=”code-add”]
var buffer = 50;
var lowX = 0 – buffer;
var lowY = 0 – buffer;
var highX = PS.screenW + buffer;
var highY = PS.screenH + buffer;
[/sourcecode]
We use a buffer to allow the tiles to go just beyond the screen boundaries so that they don’t disappear as soon as they hit the edge, and the value we give the buffer is just enough for the tiles to leave the viewport for good, regardless of their size.
We then add the code that tests whether the tile is beyond the screen boundaries. We do so by checking if the x
coordinate is less than the lower boundary or greater than the higher boundary, and the same for the y
coordinate. If the tile has left the screen, we first call removeChild
on it’s parent to remove the node from the DOM, and then call splice
to remove it from the array. Our DOM stays clean as elements get destroyed, and our array stays a manageable size as elements get removed, and memory is freed up.
[sourcecode language=”javascript” firstline=”144″ classname=”code-add”]
var x = PS.tiles[tile].x;
var y = PS.tiles[tile].y;
if( x < lowX
|| x > highX
|| y < lowY
|| y > highY ) {
// if so, then destroy it
// first remove it from the screen
var content = document.getElementById(‘content’);
content.removeChild( PS.tiles[tile].node );
// then remove it from the array
PS.tiles.splice( tile, 1 );
} else {
PS.tiles[tile].draw();
}
[/sourcecode]
For some extra variation, let’s add a random class to our tiles as we create them. We’ll first define our CSS styles:
[sourcecode language=”css” firstline=”25″ classname=”code-add”]
.variation0 {
font-size: 12px;
color: rgb(100,100,100); /* grey */
}
.variation1 {
font-size: 16px;
color: rgb(1,208,6); /* green */
}
.variation2 {
font-size: 18px;
color: rgb(254,31,124); /* pink */
}
.variation3 {
font-size: 14px;
color: rgb(100,217,235); /* blue */
}
.variation4 {
font-size: 11px;
color: rgb(202,22,227); /* purple */
}
[/sourcecode]
Then, back in our actionMouseMove(e)
function, before assigning the new tile class name, we’ll create a random number from zero to four, and create our random class name:
[sourcecode language=”javascript” firstline=”106″ classname=”code-remove-no-margin”]
newTile.className = ’tile’;
[/sourcecode]
[sourcecode language=”javascript” firstline=”106″ classname=”code-add”]
// create random class
var rc = Math.floor( Math.random() * 5 );
var cn = ‘variation’ + rc;
newTile.className = ’tile ‘ + cn;
[/sourcecode]
Conclusion
In this tutorial we have looked at creating a custom class, and instantiating many copies of that class. We programmed the object to retain properties, use own functions to update its properties, and update elements on the screen. We looked at two different notations for defining objects – the function Class() {...}
notation, and the var obj = {};
notation. We discussed using the keyword this
in the context of an instance, will reference that specific instance. We discussed referencing the object’s properties using the obj.property
syntax, and executing functions using the obj.function();
syntax.
What we haven’t discussed are ways of making the variables inside your objects private. As you saw in this example, we were able to access the coordinates of each tile by saying tile.x
. In the next tutorial, we’ll look at object privacy, and how you can keep private data hidden inside your objects.