I’m a bit short on time this week so I’ll keep it short. Last week I talked about Prototype in JavaScript, a fundamental JavaScript feature that too few programmers know about. Today I’d like to talk about a few more features that people either don’t know about or are confused about, apply(), call() and bind().
As usual I have the examples on GitHub in the apply-call-bind repository.
Also, in case you missed it, I have a free Udemy course on ReactJS and Flux for you. Only 50, so you best be quick to get it!
Function invocation
So let’s talk a bit about functions and how they can be invoked. Let’s look at a simple function with a simple invocation.
var sum = function (a, b) {
return a + b;
};
var x = sum(1, 2);
console.log(x);
It doesn’t get simpler than this and x will have the expected value of 3. Let’s make that slightly less simple. We want to be able to add an undefined number of numbers! So how would we handle that? How about this?
var sum = function (numbers) {
var result = 0;
numbers.forEach(function(n) {
result += n;
});
return result;
};
var x = sum([1, 2, 3, 4]);
console.log(x);
That looks good, right? Unfortunately, invoking the function is now more difficult as we always need an array. What if I told you there is another way?
Remember that this is JavaScript and JavaScript is, well, weird. We can call a function with any number of input parameters we want. Too few and our function might fail, too much and the extra parameters will be ignored.
There is sense in all this though. Within each function you can inspect the input parameters through an array-like object, arguments.
Let’s rewrite the first version of sum, but using the arguments list this time.
var sum = function () {
return arguments[0] + arguments[1];
};
var x = sum(1, 2);
console.log(x);
But if we can do that we can use ANY number of parameters! Arguments is array-like, which mean we can loop through the values (it doesn’t have a forEach method though).
var sum = function () {
var result = 0;
var i = 0;
for (i; i < arguments.length; i++) {
result += arguments[i];
}
return result;
};
var x = sum(1, 2, 3, 4);
console.log(x);
That’s pretty nice! We can now use the same simple syntax for any number, or a variable number, of input parameters. So now I’m going to be a real pain in the ass… We got this nice function that takes any number of parameters in the plain and simple syntax we’re used to, but… I want to pass in an array!
Apply
If I were your manager you’d be annoyed at best. I asked for plain and simple syntax, you deliver, and now I want an array after all!? Worry not! Your function is awesome and it can stay. What we’re going to use is a function on the function prototype (meaning all function objects have this function defined), apply.
So that sounded weird… Functions have functions? Yes they do! Remember that a function in JavaScript is just an object. To give you an idea, this is how it looks.
myFunc();
myFunc.someFunc();
So let’s look at this function, apply, which allows you to pass input parameters to any function as an array. Apply has two input parameters, the object on which you want to invoke the function (or the object this points to in the function) and the array with input parameters to the function.
So let’s invoke our function, sum, using apply. Notice that the first parameter can be null as we’re not invoking sum on an object.
var x = sum.apply(null, [1, 2, 3, 4]);
console.log(x);
So that’s awesome, right? Let’s look at an example that doesn’t use the arguments variable.
var steve = {
firstName: 'Steve',
lastName: 'Ballmer',
doThatThing: function (what) {
console.log(this.firstName + ' ' + this.lastName
+ ': ' + what + '! ' + what + '! ' + what + '!');
}
};
steve.doThatThing.apply(null, ['Developers']);
steve.doThatThing.apply(steve, ['Developers']);
So in this example the function doThatThing is just a regular function with a regular named input parameter and we can still invoke it using apply. In this case we also need to pass in the first parameter to set this. If we don’t specify the first parameter firstName and lastName in the function will be undefined.
We can also put in another variable as first input parameter.
var sander = {
firstName: 'Sander',
lastName: 'Rossel'
};
steve.doThatThing.apply(sander, ['Blogs']);
That’s a bit weird, isn’t it? Even though Sander does not have the doThatThing function we can invoke it as if Sander did. This can be very useful behavior as we’ll see in the next section!
Call
Another function, which looks like apply, is call. Call allows you to simply invoke a function, but makes you specify the object which serves as this within the function, much like apply. The only difference between apply and call is the manner in which input parameters are supplied. Apply needs an array with values, call needs the values separated by a comma (like regular function invocations).
steve.doThatThing.call(null, 'Developers');
steve.doThatThing.call(steve, 'Developers');
steve.doThatThing.call(sander, 'Blogs');
So why would you want to do this? It seems it only obfuscates the code… Consider the following scenario, you have some function that takes a callback as input and invokes it.
var someFunc = function (callback) {
console.log('this is ' + this);
callback('This');
};
someFunc(steve.doThatThing);
What will it print? Undefined undefined… Sure, when doThatThing is invoked as callback() this will be the global Window object. We have three options now… I’ll discuss two now and I’ll give the third in a minute.
So first we could make it so that this is not used in the callback. For this we’d need to create a closure, or a new function that invokes doThatThing on steve as if it were a normal function.
someFunc(function (what) {
steve.doThatThing(what);
});
It works, but its a lot of bloated code, so we don’t really want that. So there’s a second option, we allow the this object to be passed to the someFunc as an input parameter. And then we can invoke the function on that object using call (or apply)!
var someFuncThis = function (callback, thisArg) {
callback.call(thisArg, 'This');
};
someFuncThis(steve.doThatThing, steve);
A lot of JavaScript libraries and frameworks, like jQuery and Knockout.js, use this pattern, passing in this as an optional input parameter.
Bind
If you’ve been paying attention you’ll remember I just said there was a third method to solve our little problem with this in the callback function. Next to apply and call functions also have a bind function. Bind is a function that takes an object as input parameter and returns another function. The function that is returned invokes the original function with the input object as this context. Let’s just look at an example.
someFunc(steve.doThatThing.bind(steve));
someFunc(steve.doThatThing.bind(sander));
And it even works if your original this isn’t the function itself.
var f = steve.doThatThing;
f = f.bind(steve);
someFunc(f);
You’ll need this when passing JavaScript native functions as callbacks to third party libraries.
someFunc(console.log);
someFunc(console.log.bind(console));
The first call fails with an “Illegal invocation” because this inside the log function has to be console, but it isn’t if it’s invoked as callback. The second line works as we’ve bound this to console.
So here are three functions that all invoke a function slightly differently than what you’re used to. All three are indispensable when you’re doing serious JavaScript development though.
This blog is a bit shorter than usual, but, as they say, it’s quality over quantity. Hope to see you back again next week!
Happy coding!
The post Apply, Call and Bind on JavaScript functions appeared first on Sander's bits.