I can’t believe that I haven’t posted an article on how to call functions in JavaScript and what this
gets set to for each of the various invocation patterns. It’s one of those things that catches people out periodically, so it’s well worth discussing at length.
There are four different ways to call or invoke a JavaScript function: method invocation, function invocation, constructor invocation, and apply invocation. Each of these invocation patterns results in this
being set to a different object inside the function. In C#, there is never any doubt what this
refers to: it’s the object (the instance) on which the method is called. Using this
is entirely optional, unless you are trying to resolve some possible ambiguity in the code (say, a parameter to the instance method has the same name as a field of the instance: in this case, to refer to the field you would have to use this
). Of course, for a static method rather than an instance method, using this
is an error and will be caught at compile-time.
Back to JavaScript. We’ll first talk about the equivalent to C#’s calling of an instance method: method invocation. This is just like the C# case: you’ve got some object and you call a method of that object. The this
variable inside the function will be set to point to the object you’re calling the method on. In essence, pretty much exactly the same as C#.
Let’s look at a simple object called printer
. When you call the log
method of printer
passing in some other object, it will print the object passed in as well as the number of times the printer
object has been used.
var printer = { callCount : 0, log : function(obj) { console.log("---"); console.log(obj); this.callCount += 1; console.log("--- (log number: " + this.callCount + ")"); } };
As you can see, a very simple object with one field (callCount
) and a log
method. The log
method will print the object, increment the field, and then print a line showing how many times it has been called. The use of this
is not optional as in C#; it must be used, otherwise the interpreter will try to resolve it by walking up the scope chain to the Global Object (which in browser JavaScript is window
). If you like, this
bypasses the normal scoping rules in JavaScript.
Here’s a quick example of how to call it.
var someObj = {a:42}; printer.log(someObj); // prints 1 as log number printer.log(someObj); // prints 2 as log number
The fun thing about JavaScript is that functions are objects. We can pass them around at will. Here’s a new (empty) printer object that I then create the same field for. I can then create a log
method by copying the printer.log
function.
newPrinter = {};
newPrinter.callCount = 41;
newPrinter.log = printer.log;
newPrinter.log(someObj); // prints 42 as log number
The next way of calling a function is using function invocation. This is easy in concept – just call the bare function, not using an object – but it is the invocation pattern that causes the most problems and bugs. The reason is that the this
variable gets set to the Global Object. Let’s take a look at a couple of examples. Firstly though, let’s define a simple function that returns true
if the passed in object is the Global Object.
var isGlobalObject = function(obj) { return (function() {return this === obj;}()); };
What? Did he say simple? Let’s take it slowly. First of all, isGlobalObject
takes in an object and returns something. What it returns is the result of automatically calling an anonymous function. This anonymous function is called using the function invocation pattern we’re currently discussing and checks to see if this
is equal to the outer obj
parameter. In other words, this anonymous function makes use of the pattern we’re trying to demonstrate. (Note: because I did not hard-code window
, this function will also work on node.js, where the Global Object is called something else.)
Before making moving on, let me emphasize a point. Automatically calling an anonymous function is one of the great patterns of modern JavaScript and hence in one of these types of functions this
will point to the Global Object.
Now we can make up some example function and show that calling it using the function invocation sets this
to the Global Object.
var doSomething = function (arg) { isGlobalObject(this) ? console.log("In this function, 'this' is the Global Object") : console.log("In this function, 'this' is something else"); }; doSomething(); // prints that 'this' is the Global Object
There is another common JavaScript coding pattern that can throw up bugs because people assume this
points to the enclosing object rather than the Global Object, and that’s with callbacks. Let’s illustrate with setTimeout
. I’ll write a ticker object that outputs an asterisk once a second.
var ticker = { show : function() { console.log("*"); }, start : function() { this.show(); setTimeout(function() { this.start(); }, 1000); } }; ticker.start();
If you run this code, you should see an asterisk, and then after a second you should get an error saying “this.start is not a function”. The reason is that the callback to the setTimeout
function is an anonymous function and setTimeout
’s code will call it using the function invocation pattern. The this
variable will be set to the Global Object and not the ticker
object, despite what a quick glance at the code might imply. Since window.start
does not exist, you get the error message.
To solve this bug, we shall have to save the value of the this
variable in the start
method, and then use that local variable inside the anonymous function. Because in JavaScript scope is defined by function, the anonymous function will ‘see’ this local variable in its parent function.
var ticker = { show : function() { console.log("*"); }, start : function() { var self = this; self.show(); setTimeout(function() { self.start(); }, 1000); } }; ticker.start();
Notice that every time start
is called, it is called using the method invocation pattern and we always save the object it’s called on in a local variable called self
.
The third way of calling a function is using the constructor invocation pattern. The first point to make here is that you must use the new
keyword when calling the function.
var Person = function(lastName, firstName) { this.firstName = firstName; this.lastName = lastName; }; Person.prototype.print = function() { var name = this.lastName + ", " + this.firstName; console.log(name); }; var me = new Person("Bucknall", "Julian"); me.print();
Here I’ve declared a function called Person
, created a method via its prototype object, and then called the function using new
. What this does is similar to C#: it creates a new object based on the template I gave. Person
is not a class, even though creating a Person object looks like you’re creating an instance of a class. In essence, the new
keyword will create a brand new empty object, set its constructor to the function, and then call the constructor. The this
variable will be set to this new empty object.
The final unusual thing about constructor invocations is that the newly created and initialized object is returned by default; you don’t have to code up a return statement explicitly.
The big problem about the constructor invocation pattern is that you sometimes forget to use the new
keyword. Constructors are normal functions, there’s nothing in the language that marks them as being anything special, and so suddenly the call turns into a function invocation. And we know all about those: this
points to the Global Object. In my example: calling Person
without new
will create a two new properties on the Global Object (firstName
and lastName
), return and set me
to undefined
, and then crash on the next statement since undefined
certainly has no method called print
.
There is a bit of a hack that can get round this for you. Define your constructors like this, and you’ll avoid the problem of forgetting to use new
.
var Person = function(lastName, firstName) { if (!(this instanceof Person)) { return new Person(lastName, firstName); } this.firstName = firstName; this.lastName = lastName; };
In other words, if the this
parameter is not an instance of Person
assume that the caller forgot the new
keyword and construct a new object properly and return it. If not, just proceed as normal for a constructor.
The final way to call a function is to use the function’s apply
or call
methods. This is known as apply invocation. Yes, a function is an object, so it can have (and has) properties and methods of its own. Both apply
and call
work by calling the function on an explicit object that you supply. Arguments to the function are either passed one-by-one (call
) or as an array (apply
). (The way I remember which is which is to say Apply uses an Array because they both start with A.) The this
variable is set to the first parameter of the call to call
or apply
. Here it is in action with our printer
object from above:
var anotherPrinter = { callCount : 23 }; var someObj = { foo : 42 }; printer.log.call(anotherPrinter, someObj); printer.log.apply(anotherPrinter, [someObj]);
All that’s happening here is that I’ve created an object (anotherPrinter
) with a callCount
field, and then applied the printer.log
method to it, passing in some object to log.
2 Responses
#1 J.D. Mullin said...
20-Dec-11 8:51 AMGood article, Julian. Any comments or patterns applicable to callbacks you provide to third party components? For example, in a button click handler "this" is often the button, not your object. These callbacks can often be confusing when they live in the same js file with the object (perhaps that's the problem...). Multiple functions/methods in the same js file then have a different context for "this". Thoughts?
#2 julian m bucknall said...
03-Jan-12 12:08 PMJ.D.: I would have to say that if you were writing a library where it's likely that a
this
problem would occur ("I'm expectingthis
to be the button but it'swindow
dammit") you should invoke the callback through the apply invocation and not simply call it. I talked about this kind of pattern here.Cheers, Julian
Leave a response
Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.
_emphasis_
**strong**
[text](url)
`IEnumerable`
* an item
1. an item
> Now is the time...
Preview of response