Tuesday, December 3, 2024

Javascript Basics Part 9

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

Article #8 covered the basics of object-oriented programming in JavaScript. This article is going to continue on that path teaching you methods of inheritance as well as the usefulness (and dangers) of closures.

Inheritance

In our last article we created a ‘Cat’ function:

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!'); }
}

We can now create as many cats as we want, but what if we want to create another type of object, a dog for example? We would need to create an entirely new function with its own prototypes. If our two objects share a lot of functions in common (we could add sleep, eat and play functions, for instance) we would have a lot of unnecessarily repeated code. So what’s the solution? A concept called inheritance.

Basically inheritence allows for “parent” and “children” objects. A child shares all of the properties of its parent. We could create an “Animal”, “Pet” or “Mammal” function, for instance. Both of our Cat and Dog function would share many properties from the Animal parent function and we would only have to write our code once.

The problem is that JavaScript doesn’t really have inheritence built-in so we have to reproduce this functionality ourselves. There are several different ways to do this. One way is to use the “call” function. Call allows us to call a function from the context of another, i.e. we can specify what the “this” keyword runs as. By using “call” we can write an Animal class and then call it from a Cat or Dog class.

function Animal(name){
	this.name = name;

	this.species = 'Animal';
	this.sleep   = function(){ alert(this.name+' falls asleep: Zzzzz'); }
}
function Cat(name){
	Animal.call(this, name);

	this.talk = function(){ alert('Meow!'); }
}
function Dog(name){
	Animal.call(this, name);

	this.talk = function(){ alert('Woof!'); }
}

var sam = new Cat('Sam');
var joe = new Dog('Joe');
sam.sleep(); // Sam falls asleep: Zzzzz
joe.sleep(); // Joe falls asleep: Zzzzz

sam.talk();  // Meow!
joe.talk();  // Woof!

Although this works we are a bit limited in what we can do with it. Prototyping doesn’t work using this method, for instance: any prototypes set on Animal will not carry over to the Cat or Dog functions. As you learned in the last article, functions internally defined with “this.” have a new instance created each time you create a new copy of the parent. In this case each time we create an Animal, Cat or Dog function there is a new copy of the species and sleep functions. As you can imagine this isn’t a very efficient way to do things.

A better alternative is to prototype the entire parent class onto the child class. Doing this gives us access to all of the properties and methods of the parent class:

function Animal(name){
	this.name = name;
}
Animal.prototype = {
	species: 'Animal',
	sleep  : function(){ alert(this.name+' falls asleep: Zzzzz'); }
}

function Cat(name){
	Animal.apply(this, arguments);
}
Cat.prototype         = new Animal;
Cat.prototype.species = 'Cat';
Cat.prototype.talk    = function(){ alert('Meow!'); }

function Dog(name){
	Animal.apply(this, arguments);
}
Dog.prototype         = new Animal;
Dog.prototype.talk    = function(){ alert('Woof!'); }

var sam = new Cat('Sam');
var joe = new Dog('Joe');
sam.sleep(); // Sam falls asleep: Zzzzz
joe.sleep(); // Joe falls asleep: Zzzzz

alert(sam.species); // Cat
alert(joe.species); // Animal -- no species is defined for Dog

We could then continue on and make seperate functions for different breeds of dogs or cats, etc.

Closures

Closures are one of Javascript’s most powerful features. Attempting a simple explanation, closures bind together internal and external variables in a function. Why are they so powerful? Well put simply closures allow us to emulate almost any feature in any programming language, even if it doesn’t exist in JavaScript. That sounds a bit complicated so let’s start with a simplier example:

function beginAdding(a){
	a *= 5;
	return function finishAdding(b){ alert(a+b); }
}

var add = beginAdding(10);

add(20); // 70

Examine the above code and you’ll see that our ‘a’ variable, 10, is being passed into our beginAdding function, while our ‘b’ variable, 20, is being passed into our finishAdding function.

So what does our ‘add’ variable contain? It contains the finishAdding function with a copy of the entire beginAdding function bound to it. A copy of the ‘a’ variable from beginAdding is stored in memory for future use.

Now that you know what a closure is, we need to discuss memory leaks before we proceed. Internet Explorer, in particular, is prone to some very nasty memory leaks when it comes to closures. To understand why you need to understand Internet Explorer’s garbage collection, that is how it handles cleaning up items in memory.

Internet explorer has two seperate garbage collectors. One for JavaScript and one for DOM objects. When you unload a page it goes through and deletes all of the JavaScript and all of the DOM objects from your page. Leaks occur when you have circular references from a DOM object to Javascript back to the DOM object, or from JavaScript–>Dom–>Javascript. Internet Explorer gets confused and winds up not deleting any of the objects in the circular reference.

var someInput    = document.getElementById('inputbox');
var someFunction = function(){
	alert(someInput.value);
}

someInput.onclick = someFunction;

Here we have an infinite loop in terms of closures. Our someInput DOM object is involved in a closure with the someFunction function, and visa-versa. The browser can’t figure out which to delete first so we get a memory leak.

You’ll often create closures without ever realizing it. Take a simple function for example:

function Animal(name){
	this.sleep = function(){ alert(name+' falls asleep: Zzzzz'); }
}

You may not have even noticed it, but the ‘name’ variable in the sleep function is coming from the parent Animal function. This creates a closure.

Even defining the simplest of functions can create closures:

var x = 5;
var n = function(){
	y=10;
	return y;
}

It doesn’t seem like we are creating a closure here, but we are. Why? When we create our function it gets a reference to all variables in its current scope, so we are creating a new reference to our x variable.

Now we come to memory scope. Each function has its own scope in which it runs. All of the scopes in your code are kept in an internal memory stack. When you create a closure, the closure has access to one of these scopes. If you create multiple closures in the same scope, each closure will actually point to the same copy of each scope variable. For example:

var x       = 5;
var alertX1 = function(){ alert(x); }

x           = 10;
var alertX2 = function(){ alert(x); }

alertX1();
alertX2();

Both of the alerts in this case will be 10. Why? Because they’re both pointing to the same copy of x:

If we change x at any point, both of our alerts will reflect this. How do we fix this? The simplest way is to change the closure scope:

function makeClosure(x){
	return function(){ alert(x); }
}

var x       = 5;
var alertX1 = makeClosure(x);

x           = 10;
var alertX2 = makeClosure(x);

alertX1(); // 5
alertX2(); // 10

This solves our problem. If, however, this code was involved in a memory leak it would leak far more than our previous example. We also use more memory than our previous example. It turns out that we actually have three different scopes in this simple example:

You’ll notice that the x variable is copied into each of our two new scopes. This is because x is a string or a number. JavaScript will always pass strings and numbers by value–that is, it will always make a copy of the variable. Objects are another matter. If x were a function, an array or a generic object, we would still be referencing the same copy of x in our two functions and both of our alerts would be the same:

function makeClosure(x){
	return function(){ alert(x.val); }
}

var x       = {val:5};
var alertX1 = makeClosure(x);

x.val       = 10;
var alertX2 = makeClosure(x);

alertX1(); // 10
alertX2(); // 10

What’s different here? x is now an object. Each of our additional scopes points back to the same copy of x. When we change the value of x, it is changed in every scope in which x is referenced.

How do we avoid memory leaks with closures? We have to avoid having circular references. The most common place for memory leaks to occur is when attaching events to DOM objects, such as an onclick event. Often, completely innocent looking code will create a leak and we may never notice it. Fortunately, leaks are fairly easy to test for. If your application is leaking objects its memory usage will steadily increase every time you refresh the page. Unfortunately, tracking down that leak is an entirely different issue, but at least you are now aware of the problem!

That’s it for this article and for our lesson on Object oriented design. Our next article will cover the basics of AJAX applications!

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured