Neil de Carteret - This is why JavaScript gets a Bad Name
It's not his fault - we made him that way.
n3dst4
[info]n3dst4
Add to Memories
Tell a Friend
This is why JavaScript gets a Bad Name
Do you know why programming in Perl, Python, or Ruby is so much more fun than programming in VB, ColdFusion, or PHP?

It's quality of the community. Not the size. The quality.

I've written all six of those languages in production, and they are all roughly equivalent in capability. They all have their problems, too: VB and PHP are hampered by poor design decisions, ColdFusion is verbose yet inexpressive, Perl is getting a bit long in the tooth, Ruby is slow, and Python has a stick up its ass.

But the difference is this: when I Google for a problem in VB, CF, or PHP, I get hundreds of results from sites with names like "devmaster.com" and "awesomescripts.com". All of these sites are laden with Flash adverts and written by incompetent clowns who I wouldn't trust to write a "Hello World" correctly.

Perl, Python and Ruby, on the other hand, probably have fewer lines of text devoted to them on the web, but what lines there are of a much higher quality. Fewer adverts, more brainpower.

This is what in some circles is known as the signal-to-noise ratio.

What I'm coming round to is that JavaScript has a foot firmly planted in both camps at the moment. Its role as the de facto client-side language of the web means that hordes of useless twunts have latched onto it and excreted thousands of badly written "code snippets". But at the same time, some very intelligent people have also recognised that it's a beautiful, expressive language and deserves to be treated as such.

For today's practical class, we'll take the following question: How can you refer to "this" inside a function passed to setTimeout()?

For example:
// This constructor takes a DOM element and creates an object on which 
// you can call .notify(msg), which fills the element with the message,
// and then 10 second later, blanks it out.
function Notifier(element)
    {
    this.elem = element;
    this.notify = function(msg)
        {
        this.elem.innerHTML = msg;
        setTimeout(function() // anonymous function starts here
            {
            this.elem.innerHTML = "";
            }, 10000);
        }
    }

Except that doesn't work, because when the anonymous function is called, this has gone out of scope, so this.elem doesn't exist.

If you try to Google for the answer (feel free), you get a particularly clueless code site which says:

The trick is to retain a reference to 'this' by the use of a global variable

Followed by a 32-line version of the following:
// FOR THE LOVE OF GOSH, DON'T ACTUALLY DO THIS!
var myStupidGlobal;
function Notifier(id)
    {
    this.elem = getElementById(id);
    this.notify = function(msg)
        {
        this.elem.innerHTML = msg;
        myStupidGlobal = this
        setTimeout(function() // anonymous function
            {
            myStupidGlobal.elem.innerHTML = "";
            }, 10000);
        }
    }

CONCURRENCY, YOU BLOODY IDIOT. JavaScript is an incredibly thread-friendly language. Plonking something into global context is just asking for collision when another instance of the same class changes its value before the timeout has run.

It's exemplary of a sort of ignorant, ham-fisted programmer, the sort to whom it hasn't even occurred that just maybe, JavaScript's language designers had already thought of that and done something about it.

The answer, as you will find on the second page of search results, is to make a closure: a function which refers to a variable outside its own scope and thus hangs onto a persistent reference to it. All you need to do is assign this to a var just before setting the timeout.
// Use a local var like this to create a closure
function Notifier(id)
    {
    this.elem = getElementById(id);
    this.notify = function(msg)
        {
        this.elem.innerHTML = msg;
        var myNiceLocal = this;
        setTimeout(function() // anonymous function
            {
            myNiceLocal.elem.innerHTML = "";
            }, 10000);
        }
    }

Now your callback function gets squirrelled away with a nice safe reference to the object which created it, which won't get clobbered by some other drunken gorilla of an object running the same method.

Tags: , ,

Other pages
On this page
calendar
Back February 2008
12
3456789
10111213141516
17181920212223
242526272829
tags