Nov 28 2008

JavaScript Closures

Category: JavaScriptJonathan Fingland @ 1:26 am

Closures are one of the most powerful features of JavaScript. JavaScript isn’t the only language supporting this, but it’s certainly the one I’m going to discuss here. Just for example, the same thing can be done in ActionScript 3 — if you know how that works, you know how it works in JavaScript.

Getting down to the matter at hand, closures are essentially a way of managing variable scope. First lets look at some scope basics, though.

function User(name, age, address) {
     //name, age and address are all private to the scope of this function

     this.name = name;    // public property

     this.getAge = function () { return age; }; //public, privileged method

     function getAddress() { return address; }  //private method

}

User.prototype.getName = function () { return this.name; } //public, non-priveleged method

User.prototype.getAddress = function() { return address; } //fails. address is undefined in this scope.

Just to make it clear, you can have public properties and private variables, public — privileged and non-privileged — methods, and private methods. Privileged methods can access the variables and methods in the constructor (as well as the public ones), whereas non-priveleged methods can only access public members and methods.

Last week, I wrote this:

var myButton = document.getElementById("myButton");
myButton.addEventListener("click",
                               function (event) {
                                     alert("you clicked!");
                               },
                               false);

Now lets combine the two ideas:


function User(element, name, age, address) {
     //name, age and address are all private to the scope of this function

     this.name = name;    // public property

     this.getAge = function () { return age; }; //public, privileged method

     function getAddress() { return address; }  //private method

     element.addEventListener("click", function (event) { alert(this.name + "\n" + age + "\n" + getAddress()); }, false);

}
var myButton = document.getElementById("myButton");
var jon = new User(myButton, "Jon", 98, "123 fourth street, yourtown");

Now, if I click on myButton I’ll get an alert with all of the information…. except name. When the event handler executes, the value of this has changed. The easiest way to get around this is by taking advantage of closures and not use this by using var self = this;


function User(element, name, age, address) {
     //name, age and address are all private to the scope of this function

     this.name = name;    // public property

     this.getAge = function () { return age; }; //public, privileged method

     function getAddress() { return address; }  //private method
     var self = this;
     element.addEventListener("click", function (event) { alert(self.name + "\n" + age + "\n" + getAddress()); }, false);

}
var myButton = document.getElementById("myButton");
var jon = new User(myButton, "Jon", 98, "123 fourth street, yourtown");

Now it works. it’s a very small change and it means that by taking advantage of closures we can maintain a reference to this even when the context changes. This technique is extremely common in event handlers, but it’s also very useful when using setTimeout or setInterval (which use window as context)

Another use of this is to wrap functions that can see internal data. I’ll explain that more in the next post.


Nov 14 2008

Extending Prototypes

Category: JavaScriptJonathan Fingland @ 10:14 am

JavaScript is described as a prototypal language because of it’s use of a shared prototype/cookie cutter for objects. It’s probably easier to understand with some examples, though.

function User (name) {
    this.name = name;
}

var jon = new User("Jon");
alert(jon.name);

The above will show an alert dialog with the name of the user, “Jon”. All instances of User will have a name property, but we can can do more:

function User (name) {
    this.name = name;
    this.say_name = function() {
        alert(this.name);
    }
}

var jon = new User("Jon");
jon.say_name();

Now all users will have a method, say_name. Unfortunately, every User instance has a new method called say_name. It's not a complex function, but there's potentially a lot of needless duplication. If say_name doesn't need to know private details, we can do the following instead:

function User (name) {
    this.name = name;
}

User.prototype.say_name = function() {
    alert(this.name);
}

var jon = new User("Jon");
jon.say_name();

Now all User instances share the same say_name function.

A couple of days ago, I posted about First-class functions in JavaScript and in particular the map function. As written, it works fine, but how could we take advantage of the prototype property shown above. I mentioned that JavaScript 1.6 adds support for a map function, but older browsers don't support it so we can do this instead:

if (typeof Array.prototype.map == "undefined") //check if it isn't already defined (e.g. IE 6)
{
    Array.prototype.map = function (callback) {
        var ret_array = [];
        for(var index = 0, length = this.length; index < length; ++index) {
            ret_array[index] = callback(this[index]);
        }
        return ret_array;
    }
}

Now there is one small problem. The map function defined in JavaScript 1.6 takes an optional 2nd parameter for the context of this. We can fix the above by changing the method as follows:

    Array.prototype.map = function (callback, context) {
        var ret_array = [];
        for(var index = 0, length = this.length; index < length; ++index) {
            ret_array[index] = callback.call(context, this[index]);
        }
        return ret_array;
    }

I've used the call method which belongs to all Function objects. See call at MDC for more detail on that. It allows you to change the context of the execution of a function... letting 'this' refer to something different. I'll write more about that later as it really deserves its own post.

So, now we have a standard compliant, backwards-compatible (back to JavaScript 1.3 compliant browsers) map function. If the browser supports it, the native map function will be used, and if not, our substitute will be used instead. Feel the Win.

There's a lot more you can do with this, but that should be enough to get started.


Nov 12 2008

First-class functions in JavaScript

Category: JavaScriptJonathan Fingland @ 1:31 am

In JavaScript functions are first class objects. JavaScript isn’t alone in this by any means, but it’s a very powerful feature that a lot of beginners are unaware of.

To start off, think of a variable declaration:

var room_number = 1406;

and you can pass that into any function, like so:

alert(room_number);

Well you can do the same thing with functions. Basically there are two common ways to create functions, and another less common way. Everybody reading this is probably familiar with this form:

function say_name(name) {
    alert('Your name is ' + name);
}

Obviously we’ve created a function called ’say_name’ and if I want to use it, simply call say_name("Jon"). However we can accomplish the same thing with the following:

var say_name = function(name) {
    alert('Your name is ' + name);
}

In this case we created an anonymous function and then gave it a name. Another nice feature here is that we can do the same thing when adding an event handler — just pass the anonymous function. Look at the two snippets below:

function announce_click(event) {
    alert("you clicked!");
}

var myButton = document.getElementById("myButton");
myButton.addEventListener("click",announce_click,false);

Passing the function name to addEventListener, and…

var myButton = document.getElementById("myButton");
myButton.addEventListener("click",
                               function (event) {
                                     alert("you clicked!");
                               },
                               false);

…passing an anonymous function to do the same thing.

A word of caution though. When using removeEventListener, you must pass it the same function. Another anonymous function that does the same thing is not good enough. So if you never need to remove an event handler then declaring the function in the handler works great. Otherwise, it’s often a good idea to give the function a name so you can refer to it later.

Well, that’s all fine and dandy, but what else can you do with it? Well, it’s a natural fit for a callback. For those not familiar with the term callback, it’s simply a way of telling the function you’re calling to call another function as well. In the examples above, addEventListener took a callback in the 2nd parameter.

If you’re familiar with PHP’s callback system of passing a string with the name of the callback, rest easy. This is much better. JavaScript 1.6 has a map function on Arrays, but since backwards compatibility is often critical, I could use something like the following:

function map(callback,array) {
    var ret_array = [];
    for(var index = 0, length = array.length; index < length; ++index) {
        ret_array[index] = callback(array[index]);
    }
    return ret_array;
}

The map function will loop through the elements of the array and, for each one, call the function callback and pass it the value of the element. Then it assigns the value returned by callback to the matching index in a new array. Finally, it returns the new array.

The important thing here is that callback could be just about anything. Consider this:

function square(number) {
    return number * number;
}

var my_array = [1,2,3,4];

var squared_array = map(square, my_array);
//squared_array contains [1,4,9,16]

As you can see, using the map function we were able to easily apply the same function to every element of the array and get an array of the return values. Obviously the possible applications are pretty much endless. Have fun with it. Just remember that functions are objects too -- which brings me to the third, much less common way of creating functions. I can't say as I've ever seen this in the wild, and I don't recommend using it, but for completeness here it is:

var say_name = new Function("name", "alert('Your name is ' + name);");