Home & blog  /  Tag: context  /

The magic of JavaScript's apply()

posted: 23 Jul '12 14:10 tags: JavaScript, apply(), context, PHP, Stack-Overflow

I recently answered a Stack Overflow question re: how to call a function in PHP with a dynamic number of arguments. The answer is call_user_func_array().

I mentioned in passing that the JS equivalent was apply(), and that apply() could also fake execution context. asked me to elaborate, so here I am.

Meet apply()

apply() is the clever gem of intermediate-advanced JavaScript, and is one of my favourite features of the language.

Inherited by every function/method, apply() has two uses, allowing you to:

  • set the this context in which a particular function/method executes
  • pass an array of arguments to a function that ordinarily expects each argument to be passed separately

Both of these are hugely useful and allow you to accomplish things that would otherwise require cumbersome loops or outright code re-writes.

Below I'll look at four varying use-cases for apply().

Use case 1: library APIs

Whilst 'faking' the context of a function may seem somewhat whorish at first, it can be hugely useful where libraries are concerned. Why, we need look no further than our ubiquitous friend jQuery. Consider this:

1var long_paras = $('p').filter(function() {

2     return $(this).text().length > 5;

3});

There, I select all paragraphs with text longer than 5 characters. (The filter() method, if you don't know, takes a callback function and applies it iteratively to each matched element; the element is removed from the stack if the function returns false).

But have you ever wondered why, conveniently, this inside the callback magically points to the element? There's certainly no native reason this should be so. The reason is, internally, jQuery does something like this: (Warning - guesswork ahead, but the point remains).

1jQuery.prototype.filter = function(func) {

2    

3     //for each element on the stack

4     for (var i=0, len=this.elements.length; i<len; i++)

5    

6         //apply the callback function in context of element

7         if (!func.apply(this.elements[i]))

8    

9             //if func returns falsy, remove element from stack

10             delete this.elements[i];

11};

As you can see, the callback is not merely invoked, it is applied, with each element (in turn) set as the this context. Clever, eh? If this wasn't possible, jQuery would have to pass the element as an argument to your callback instead - decidedly less graceful. The callback is all about the element, so it's sensible this points to it, just as it does natively in, say, event callbacks bound with addEventListener().

Use case 2: array as arguments

This is easily demonstrated with some built-in methods, for example Math.min(). This method, you won't be staggered to learn, finds the minimum value from within a list of numbers passed as separate arguments.

Math.min(2, 5, 7); //2

That's fine, but the chances of you ever wanting to use this method in the above way are pretty slim. Almost always, you'd have an array of numbers, probably gathered or computed dynamically, and want to find the lowest one from within that.

Other languages, such as PHP via array_min, allow this. In JS, you'll need apply() magic:

1//gather up some random numbers into an array

2var random_nums = [], num_nums = 50;

3for (var i=0; i<num_nums; i++)

4     random_nums.push(Math.floor(Math.random() * 10000));

5    

6//find lowest, with some apply() fudgery

7Math.min.apply(null, random_nums);

Since our numbers there are generated dynamically, the first approach is not an option. Because apply() takes an array and passes it to a function as though each value of that array was a separate argument being passed, this is our solution.

Note that I pass null as the first argument, since Math.min() is a static method that does not care about context - it merely deals with what it is passed as arguments.

Let's look at an example where contexts is relevant.

Use case 3: an approach to inheritance

JavaScript lacks a formal notion of classes and, therefore, classical inheritance. What it does have, and what is at the centre of the language, is prototypal inheritance. Using this, there are various approaches to simulating traditional inheritance.

One of them (though not necessarily the best one) utilises apply().

1function Parent() {}

2Parent.prototype.sayHi = function() { alert('hi'); };

3function Child() { Parent.apply(this, arguments); }

4Child.prototype = new Parent();

As I say, this is just one approach to inheritance simulation. Like all approaches, it has advantages and disadvantages. It would be off-topic to discuss them here, but there's more info on this here. Ultimately, the approach to inheritance used comes down to taste and requirement a lot of the time.

Use case 4: Converting function arguments to array

You might think this sounds like the second use-case I demonstrated, but I'm driving at something rather different here.

Inside any function, a local variable, arguments, is implicitly created. It is an array-like object (but NOT an array) of the arguments passed to that function.

1function someFunc() { console.log(arguments); }

2someFunc('apple', 'pear');

There, arguments looks and feels like a normal, indexed array - in fact it's an object, with keys named 0, 1 and 2.

A common need is to treat arguments as an array - and, happily, apply() can help us with this.

(Well, it's part of the help. The following technique also owes a lot the fact that Array's slice method is not particularly fussy about needing a real array - apparently an array-like object will do fine, also.)

1function someFunc() {

2     arguments.push('banana'); //ERROR

3     var args_as_array = [].slice.apply(arguments);

4     args_as_array.push('banana'); //OK

5}

6someFunc('apple', 'pear');

Line 2 fails because push is a method of Array, not Object. We must first 'coerce' the arguments object to an array, as we do in line 3. Normally, slice() is concerned with the extraction and return of certain elements of an array, but since we don't pass it any arguments denoting this, it simply gives us back an array in its entirety.

apply() vs. call() vs. bind()

These three tend to get lumped together because they're all concerned with setting the this context in which the stipulated function runs. Let's quickly look over some of the differences.

call() works just like apply() except arguments must be passed individually, not as an array. So it has only one benefit - context setting. With this in mind, you'll probably find yourself using apply() far more than call().

bind(), on the other hand, is concerned with the creation of new functions that are permanently bound to a particular context. Contrast this with apply(), which does not create functions - it merely invokes them.

So if you wanted to make a permanent copy of a function or method, but which runs in a different context from its original, use bind().

This is best demonstrated with reference to a common error among early JavaScript developers. Perhaps armed with the knowledge that complex types are copied by reference, not value, the following does not behave as they expect:

1var obj = {

2     property: "hello",

3     method: function() { alert(this.property); }

4}

5    

6//make a shortcut to the object's method

7var alias = obj.method;

8    

9obj.method(); //alerts hello, as expected

10alias(); //alerts undefined - context not preserved

We make a reference to the method - but the context is not stored. Therefore, when the alias is invoked, by the that time it's running in the context of the global scope, not obj.

To make the alias whilst also preserving context, bind() is needed:

1var alias = obj.method.bind(obj);

2alias(); //alerts hello - context preserved

(Sidenote: another capability of bind() is that you can specify default values for function arguments, a la partial application, but again that's off-topic for this post. For more on this very useful method, check out the excellent MDN documentation on it.)

And finally... apply() with instantiation

One of the minor drawbacks of apply() is that it cannot natively be used for instantiation. You might try something like this:

1function Dog(name, colour) {}

2var fido = new Dog.apply(null, ['Fido', 'brown']);

What we meant was to run apply() on the instantiation of the class. What actually happens, though, is JavaScript first runs Dog.apply(...) and then the result of that operation is instantiated. In short, a right mess, and not what we wanted.

Fortunately there's a couple of workarounds for this. I favour this one, demonstrated in this Stack Overflow answer:

1//set up our class and a method

2function Dog(name, colour) {

3     this.name = name;

4     this.colour = colour;

5}

6Dog.prototype.sayHi = function() {

7     alert(this.name+' says hi!');

8}

9    

10//make a new version of apply() that supports instantiation

11function newApply(classs, args) {

12     function F() { return classs.apply(this, args); }

13     F.prototype = classs.prototype;

14     return new F();

15}

16    

17//instantiate and test

18var fido = newApply(Dog, ['Fido', 'brown']);

19fido.sayHi(); //Fido says hi!

A bonus of that snippet is it demonstrates an alternate approach to simulating classical inheritance (I mentioned there were a few ways). This approach is known as the temporary constructor approach, and utilises a blank function as a 'proxy' between the parent and child classes. As I mentioned, I won't go into these here - but there's more info on them in this article.

Conclusion

So there you have it. I told you apply() was clever. A lot of what we take for granted in library APIs depends its magic. At first the concept can seem quite shocking - faking context? Isn't that bad pattern design? - but as I've hopefully shown, there are good cases for this. And there's more - I showed only a handful.

(Icon attribution: Oxygen Icons)

5 comments | post new