JavaScript series: The ThisBinding
The this
keyword in Javascript can be confusing even for experienced developers. What this is a reference to depends on how the function was called. It’s a runtime binding that has nothing to do with where the function was declared. The ThisBinding
makes it possible to use a function in multiple contexts rather than creating multiple versions of it.
To find out what the this
keyword will be a reference to you need to find the call-site of the function. In other words where the function was called either by looking at your code or through a debugger. Then you’ll have to inspect the call-site to determine which of the four rules explained below that applies.
The new binding
The first rule with the highest precedence is the new binding.
function Person(name) { this.name = name; } var personObj = new Person("Jonathan"); console.log(personObj.name); // Jonathan
When the new
operator is put in front of a function call it creates a new object and assigns it’s reference to the this
keyword. The newly created object also gets it’s prototype to reference Person.prototype which in this case is an empty object. To learn more about the prototype
read the post Prototype-based inheritance of my Javascript series.
If the function does not explicitly return an object the object this
is referencing will be returned. Should the function return anything else that is not an object that return value will be ignored and the object referenced by this
will be returned.
Explicit binding
The explicit binding forces this
to reference a specified object.
function sayHi() { console.log("Hi " + this.name ); } var person1 = { name: "Jonathan" }; var person2 = { name: "Kalle" }; sayHi.call(person1); // Hi Jonathan sayHi.apply(person2); // Hi Kalle
With .call
and .apply
we can set this
to reference the object we want on any function call(except functions that is using hard binding).
Function.prototype.apply(thisArg, [argsArray])
Function.prototype.call(thisArg[, arg1[, arg2[, …]]])
The call
and apply
function is inherited from Function.prototype. They achieve the same goal but with different signatures. The apply
function takes an array as it’s second parameter which will be the arguments for the function invoked. The call
function requires the arguments to be listed explicitly as parameters.
Hard binding
Sometimes we don’t want the caller to change the thisBinding. We can then use hard binding.
function sayHello() { console.log(this.name); } var person1 = { name: "Jonathan" }; var person2 = { name: "Kalle" }; var origSayHello = sayHello; sayHello = function() { origSayHello.call(person2); }; sayHello.call(person1); // Kalle
By saving a reference to sayHello
and then creating a wrapper function which always runs the original function with .call
and our specific object we’re throwing away whatever the sayHello.call
specifies. This way we can always be sure that the this
keyword is referencing what we want.
In ECMAScript 5 we have the built in function bind
that is specified on Function.prototype
to help us with hard binding.
function sayHello() { console.log(this.name); } var person1 = { name: "Jonathan" }; var name = "Kalle"; setTimeout(sayHello, 1000); // undefined setTimeout(sayHello.bind(person1), 1000); // Jonathan
The setTimeout
function will have it’s thisBinding
pointing at the global/window object which is the last of the four rules(Default binding, explained below). But with hard binding we force the thisBinding
to point the object we want.
Implicit binding
The third rule is the implicit binding. The rules states that the containing object is referenced by the this
keyword when a function is called.
function sayHello() { console.log(this.name); } var person1 = { name: "Jonathan", sayHi: sayHello }; var person2 = { name: "Kalle", sayHi: sayHello }; person1.sayHi(); // Jonathan person2.sayHi(); // Kalle
As we can see above it doesn’t matter were the function was declared only how it was called. So when sayHi
was called with person1.sayHi
the this
keyword is referencing person1
and on the line under it’s referencing person2
.
var personUtilities = { sayHello: function() { console.log(this.name) } }; var person1 = Object.create(personUtilities); person1.name = "Jonathan"; var person2 = Object.create(personUtilities); person2.name = "Kalle"; person1.sayHello(); // Jonathan person2.sayHello(); // Kalle
The same is true for functions in the prototype chain no matter how high up they’re defined.
The implicit binding is all about finding the nearest object and then you’ll know what this
is a reference to.
Default binding
The last catch-all rule is the default binding.
function sayHello() { console.log(this.name); console.log(this === window) } var name = "Jonathan"; sayHello(); // Jonathan, true
If none of the other three rules above matches the default binding kicks in. The example above shows us that this
is referencing the global/window object in the sayHello
function.
function sayHello() { "use strict"; console.log(this === undefined) } var name = "Jonathan"; sayHello(); // undefined
If strict mode
is specified inside the function the global/window object is not eligible for the default binding. If the call-site is running in strict mode
but not the executed function this
will reference the global/window object.
var person = { name: "Jonathan", sayHello: function() { console.log(this.name); } }; var sayHi = person.sayHello; sayHi(); // undefined
Remember that it’s all about the call-site. In the above code we created a variable sayHi
and referenced person.sayHello
. Then we called sayHi
without an object in front of it so the default binding rule applies and in this case the global/window object doesn’t have variable called name
and therefore we get an undefined
error.
Arrow functions
Until arrow functions was introduced in ECMAScript 6 every function defined it’s own this
reference. Arrow functions lexically binds this
. Meaning that this
will be the same as in it’s enclosing context.
function Person(name) { this.name = name; setTimeout(function() { console.log(this.name); // undefined console.log(this === window); // true }, 1000); } var person = new Person("Jonathan");
Without a normal callback function the default binding will apply and this
will be the global/window object.
function Person(name) { this.name = name; setTimeout(() => { console.log(this.name); // Jonathan }, 1000); } var person = new Person("Jonathan");
But with a arrow function this
will be the same as in the enclosing context.
setTimeout(() => { "use strict"; console.log(this === window); // true }, 1000);
In the above code this
is referencing the window object even though were using strict mode because this
is lexically bound.
Conclusion(tl;dr)
The this
keyword in JavaScript can be confusing but when you learn the four binding rules it’s quite straightforward. It’s all about how and where the function was called.
We have the new binding which happens when the new
keyword is put in front of any function. It creates an empty object and binds this
to it inside that function.
The explicit binding with .call
and .apply
which makes it possible to call a function with a specified object as this
.
The implicit binding that states that it doesn’t matter where a function was declared only where it was called. Find the call-site and on which object the function was called. If obj.sayHello()
was called, then inside sayHello
this
would point to obj
.
The default binding which is the catch-all rule binds this
to the global/window object(in strict mode it will be undefined) when none of the other three rules apply.
Arrow functions introduced in ECMAScript 6 is the exception to these rules above. It uses a lexical this
which means that this
will be the same as this
in the enclosing context.