Javascript Basics Part 8

By Mark Kahn

This is the eighth in a series that will introduce you to the JavaScript language.

Almost every modern programming language has some method of writing Object Oriented (OO) code. If you are familiar with C, Java, VB, php or any similiar language, you are probably familiar with the Class structure of those languages.

Put simply, Classes in these languages allow us to quickly create objects with the same properties and methods without having to redefine all of these properties and methods. In terms of structure, we have Classes then inside of this Class we have functions and variables, and inside each of these functions we have more variables. Every function in a class ideally works with variables and other functions internal to that Class.

Class structure, and objects in general, work well because they work like real life objects. We might have a Television Class, for instance. Since all televisions more or less work the same, there is no reason to redefine many attributes of our television in order to make a new one. This Class would define our television's methods and properties. We would have properties like brand name, screen size, etc and Methods like turn on/off, change channel, etc.

JavaScript differs slightly from the standard Class OO structure. Instead of using Classes to create objects, every function in JavaScript can effectively act as a class. We use nested functions a concept called Prototyping to accomplish the same thing as languages with Class structures. If you are familiar with any Class-based OO language, prototyping might seem a weird concept at first, but it offers as much power, and often much more flexibility in programming style than other methods.

new Object and Object Literals
To begin with we need to explain how Objects work in Javascript. Objects allow you to define a variable and then set any number of properties on that variable. To understand this, let's take a look at a simple example:
var myObj  = new Object;
myObj.a    = 5;
myObj['b'] = 10;
myObj.c    = 20;
myObj.getTotal = function(){
	alert(this.a+this.b+this.c);
});

// or

var myObj = {a:5, b:10, c:20, getTotal:function(){ alert(this.a+this.b+this.c); }};
Both of these snippets create an identical variable, myObj. Our first example uses the new Object() syntax and then defines each property one by one. The 2nd snippet is short-hand notation that does exactly the same thing. We now have a variable, myObj. myObj contains 3 integer variables: a, b and c. To access any of these we simply use myObj.a or myObj['a']. You'll notice that myObj also has a function as a property, getTotal. We access getTotal the same way we access our a, b and c properties: myObj.getTotal(). You'll notice that our getTotal function accesses variables in the myObj variable by using "this". When you are running code from within a function in an object, "this" refers to the object. In this case "this" refers to myObj itself.

As you can see, objects are incredibly useful in JavaScript. Unfortunately, this is often far too much information to declare each time we want a new object. For example, let's write an Animal Object:

var myAnimal = {
	name:     'felix',
	species:  'cat',
	talk:     function(){ alert('Meow!'); },
	callOver: function(){ alert(this.name+' ignores you'); },
	pet:      function(){ alert('Purr!'); }
}
We now have a variable, myAnimal, that represents a cat named felix. Unfortunately if we want to create another cat, we need to type all of this again. This is where Object-Oriented design comes in. Instead of re-typing the entire object each time, we can create a function that creates a similar object for us:
function Cat(name){
	this.name     = name;

	this.species  = 'Cat';
	this.talk     = function(){ alert('Meow!'); }
	this.callOver = function(){ alert(this.name+' ignores you'); },
	this.pet      = function(){ alert('Purr!'); }
}

var felix = new Cat('Felix');
var sam   = new Cat('Sam');
var patty = new Cat('Patty');

felix.pet();          // alerts 'Purr!'
sam.callOver();       // alerts 'Sam ignores you'.  Just like a cat!
alert(patty.species); // alerts 'cat'
In this example we create one function, Cat, and then create 3 new cats from this function: Felix, Sam and Patty. Each of these cats has the same functions: talk, callOver and pet, and each of them has their species property set to cat. And like all cats are, each of them is equally ornery when you try to call them over.

We generally say that felix, sam and patty are all instances of the same Object: Cat. The code in the Cat function itself is called a constructor. We take in our 'name' variable and use that to set this.name. Unfortunately, by declaring each function of Cat in our constructor, we are actually making a new copy of each function each time we create a new Cat. You can see how this works in the image to the left. Since our talk, callOver and pet functions are identical we actually only need one copy of each function. This is where Prototyping comes in.

Prototyping
We can re-write our Cat function so that each function is declared only once:
function Cat(name){
	this.name = name;
}
Cat.prototype.species  = 'Cat';
Cat.prototype.talk     = function(){ alert('Meow!'); };
Cat.prototype.callOver = function(){ alert(this.name+' ignores you'); };
Cat.prototype.pet      = function(){ alert('Purr!'); };
The syntax of this method is a bit different than before. Instead of declaring all of our properties and methods within our Cat function, we are now declaring all of them that are the same for every Cat with Cat.prototype. This may look more complicated, but it offers many advantages. Suppose, for instance, that we wanted to add a new function, sleep, to every cat that we have. There are two ways to do this. First, we can add a sleep function to felix, sam and patty. This, however, is not only tedious, but also inefficient. If we were to have 500 cats we would first need to keep track of these 500 cats and then add the function to each cat.

With prototypes, however, we can add our sleep function to every cat at the same time:

Cat.prototype.sleep = function(){ alert(this.name+' falls asleep'); };
Not only is this quicker, but we no longer have to keep track of every cat in order to add the sleep function.

There is a quicker way to add prototypes. By using the object literal that we saw earlier in the article, we can set as many prototype properties or methods as we want at the same time.

function Cat(name){
	this.name = name;
}
Cat.prototype = {
	species:  'Cat',
	talk:     function(){ alert('Meow!'); },
	callOver: function(){ alert(this.name+' ignores you'); },
	pet:      function(){ alert('Pet!'); }
}
It is important to note that we can only set prototypes on a function one time using this method. After that, you must use the previous method, Object.prototype.method = function(){ ... }. If we were to now try and add a sleep method to our cats using this method,
Cat.prototype = {
	sleep: function(){ alert(this.name+' falls asleep'); }
}
our previous prototypes, species, talk, callOver and pet, would all be erased. The only prototype we would now have on our Cats would be sleep.

Prototypes can also be used to extend JavaScript's built-in objects. We can implement a String.prototype.reverse function, for instance, that will return the reverse of any string we create:

String.prototype.reverse = function(){
	var out = '';
	for(var i=this.length-1; i>=0; i--){
		out+=this.substr(i, 1);
	}
	return out;
}
alert('asdf'.reverse());
This can be a very useful tool when used correctly. You can implement String.prototype.trim() to trim any whitespace off of a string, Date.prototype.dayName to return the day name from a Date object, etc. It is strongly suggested that you refrain from adding any prototypes to Array or Object, however, as doing this breaks "for-in" loops for these two data types. Consider the following example:
var myArray = [1, 2, 3];
for(n in myArray) alert(n);	// alerts 0, 1 and 2 - the indexes of the array.

Array.prototype.something = function(){ };

for(n in myArray) alert(n);	// alerts 'something', 0, 1 and 2.
As you can see, we prototyped Array and added a 'something' function. Now, however, that 'something' function is visible as an element of the array, a result that we did definitely not expect and do not want. The same thing occurs with Objects and Object Literals if you prototype Object. If you are 100% certain that you will never use a "for-in" loop and are certain that no other developer will be using your JavaScript code, then feel free to prototype Array or Object, but be aware of the issues with it. There are other methods of achieving the same results, however. My personal preference is to extend Array without using prototype:
Array.find = function(ary, element){
	for(var i=0; i<ary.length; i++){
		if(ary[i] == element){
			return i;
		}
	}
	return -1;
}

alert(Array.find(['a', 'b', 'c', 'd', 'e'], 'b'));	// alerts 1
As you can see, we now have to type Array.find(ary, e) instead of ary.find(e) as we would if we prototyped the Array Object, but these extra few characters are worth typing in order to not break existing JavaScript functionality.
Private, Public and Static variables
The way that we define variables in our Objects determines what methods our Objects have available to access those variables. In JavaScript, there are five levels of methods and properties when working with OO code.

Private Declared with 'var variableName' or 'function functionName' inside of the object. Can only be accessed by other private or privileged functions.
Public Declared with 'this.variableName' inside of the object. Can be changed by any function or method.
Privileged Declared with 'this.functionName = function(){ ... }' inside of the object. Can be accessed by any function or method and can call reference or change any Private variable.
Prototype Declare with 'Class.prototype.variableName' or 'Class.prototype.functionName'. Functions declared this way will have access to any public or prototype variables. Attempts to change variable created this way will instead create a new public variable on the object and the prototype variable will be unavailable.
Static Declare with 'Class.variableName' or 'Class.functionName'. Can be changed by any function or method. This method is rarely used.

To understand these different levels, let's look at an example:

function Cat(name, color){
	/*
	Constructor: any code in here is run when the object is created
	*/
	Cat.cats++;

	/*
	Private variables and functions - may only be accessed by private or privileged functions.

	Note that 'name' and 'color', passed into the Class, are already private variables.
	*/
	var age  = 0;
	var legs = 4;
	function growOlder(){
		age++;
	}

	/*
	Public variables - may be accessed publicly or privately
	*/
	this.weight = 1;
	this.length = 5;

	/*
	Privileged functions - may be accessed publicly or privately
	May access Private variables.

	Can NOT be changed, only replaced with public versions
	*/
	this.age = function(){
		if(age==0) this.length+=20;

		growOlder();
		this.weight++;
	}
}

/*
Prototyped Functions - may be accessed publicly
*/
Cat.prototype = {
	talk:     function(){ alert('Meow!'); },
	callOver: function(){ alert(this.name+' ignores you'); },
	pet:      function(){ alert('Pet!'); }
}

/*
Prototyped Variables - may be accessed publicly.
May not be overridden, only replaced with a public version
*/
Cat.prototype.species = 'Cat';

/*
Static variables and functions - may be accessed publicly
*/
Cat.cats = 0;
As you can see, there are many levels of permissions to consider. As we discussed earlier, all Private, Privileged and Public functions and variables are copied each time you make a new instance of your Object. In general, nearly everything you need to do can be accomplished with prototypes and public variables. For these two reasons, and because of a concept called Closures, it is generally a good idea to refrain from using Private, Privileged and Public variables and functions unless you have a specific need to do so.

That's all for this article. In our next article we will continue with Object Oriented code and discuss Closures and Class inheritence.

Make a Comment

Loading Comments...

  • Web Development Newsletter Signup

    Invalid email
    You have successfuly registered to our newsletter.
  •