Understanding the JavaScript “this” keyword: Part 2

In the previous article, we uncovered the fundamental rules around how the value of this is determined when inside a function. If you haven’t read it yet, head over to the first article, and pop back here when you’re done.

From this point onwards, we’re going to explore a number of JavaScript features that throw those rules out of the window, and allow the caller to choose the value of this.

  1. Changing the value of this using call() and apply()We know if you call someObject.foo() that this inside foo will be someObject. What if we want it to be anotherObject?

    Well, we could do this;

    anotherObject.foo = someObject.foo;
    anotherObject.foo();
    delete anotherObject.foo;

    Bit of a hack, no? Enter call() and apply(). All you have to do is;

    someObject.foo.call(anotherObject);

    What if you want to pass arguments to foo() as well? No problem!

    someObject.foo.call(anotherObject, 1, 2, "abc");

    apply() works exactly the same, but the difference is how they let you pass arguments to the function. call() expects each argument as a separate parameter, whereas apply() expects a single array, whose elements are all the parameters. The equivalent apply() call for the above would be;

    someObject.foo.apply(anotherObject, [1, 2, "abc"]);
  2. Forcing the value of this using bind()bind() is a tricky addition to the JavaScript language (introduced in ES5). If you call someObject.foo.bind(anotherObject), bind() will return a new function; and no matter how you call the returned function (even using call() and apply()), the value of this will always be anotherObject.
    var obj = {
        val: 4
    };
    
    function foo() {
        alert(this.val);
    }
    
    var bar = foo.bind(obj);
    
    foo(); // behaves as expected; alerts `undefined` as `foo` is not attached to an object
    bar(); // breaks the rules! alerts `4` because `bind()` sets `this` to `obj`.
    
    bar.call(window); // this has no effect; still alerts `4`.

    Try it yourself!

    You might wonder how bind() works; but even if ES5 hadn’t included bind(); well, we could roll out our own really easily;

    Function.prototype.ourBind = function(thisArg) {
        var that = this;
    
        return function() {
            return that.apply(thisArg, arguments);
        };
    };

    We could then use ourBind() exactly how we’d use bind(); see it in action on jsFiddle!

  3. The new keyword has the last say…One last feature in JavaScript that I’d like to mention before finishing this article is the new keyword.

    If you’re not quite clued up on the behaviour of new you can read more about it here.

    Using new when calling a function is the final way to change the value of this when calling a function. When you use new, this points to a new object, whose “prototype” is set to the prototype of the function you called new on. Yep; that might take a few reads to sink in.

    The only use case for this is when creating instances of a object; but I just wanted to complete the list of “ways-this-can-be-changed”.

That concludes the articles on this; I hope you now have a much better understanding about how it works! If you want to read a few more articles to help it sink in, I’d recommend this quirksmode article and this Stack Overflow question

jQuery delay() not working for you?

jQuery’s delay() function causes more than its fair share of problems over on Stack Overflow. You often see people trying to use delay() to delay something like* setting a CSS property (via css()), and then wondering why their CSS property updated immediately; irrespective of their use of delay().

$('#some-element').delay(5000).css("backgroundColor", "red"); // this won't work

*: Other common problems originate from trying to perform DOM manipulation (via the likes of append(), after() etc, or the setting of HTML/ values (via html(),text() or val()).

The short answer is to basically use setTimeout() instead. The long answer involves looking more into how jQuery queues these requests, and finding out exactly what delay() does…

Why it doesn’t work…

When you call delay(5000), what you’re actually doing is calling delay(5000, "fx") as the function has an optional second parameter (the queue name you wish to delay operation of) which defaults to fx. The various effects methods (fadeOut(), animate() etc.) all use this queue which is why delay() has an effect on them.

However nothing else in jQuery does (by default at least, but we’ll get to that part shortly). That is to say that calls to any manipulation functions (html(), append(), css() etc.) will completely ignore your delay() call, because they’re are all executed immediately, and not queued at all.

How you can fix it…

The recommendation here is to use setTimeout() instead; your code will end up looking like this:

setTimeout(function () {
    $("#some-element").css("backgroundColor", "red");
}, 5000);

However, if you wish to use delay(), you can use the queue() function to queue something. The signature of the queue() function is as follows;

queue([queueName,] functionToBeExecuted(next));

That is to say, the first argument is the name of the queue you wish to add to (which defaults to fx when not specified), and the second argument is the function you wish to queue. That function gets passed one parameter, which you must call when your function has finished doing whatever it needs to do; this is to ensure the next elements in the queue get executed as well.

Therefore we end up doing something like this;

$('#some-element').delay(5000, "my-queue").queue("my-queue", function (next) {
    $(this).css("backgroundColor", "red");
    next();
});

Note that in the above example, we’ve specifically avoided the fx queue; the changing of the background-color is not related to fx at all, so we shouldn’t abuse the use of that queue. Having said that, there are times when queuing to the fx queue is useful; such as when you want to perform an action after a list of effects have completed;

$("#some-element").fadeIn(200).animate({
    padding: "+=50"
}, 1500).delay(1000).queue(function (next) {
    $(this).css("backgroundColor", "red");
    next();
});

Of course, if we wanted to be very explicit about which queues we were using, it’d look something like this:

$("#some-element").fadeIn(200).animate({
    padding: "+=50"
}, 1500).delay(1000, "fx").queue("fx", function (next) {
    $(this).css("backgroundColor", "red");
    next();
});

For further reading after finishing the article, consider reading the delay() documentation or the Stack Overflow question that triggered me writing this article!