What is this
December 21, 2019
If you’re a Java or C# programmer coming to JavaScript, forget all you know about this. In JavaScript, the value of this is unfortunatly not very obvious, since it’s determined by how we choose to invoke a function.
There’s 4 ways to invoke a function and we’ll go over each one:
- As a function
myFunc()
- As a method
myObj.myFunc()
- As a constructor
const object = new myFunc()
- Via the functions apply or call methods
myFunc.call(myObj)
So what is this? Finding out what this is, is easy. We’ll just log it:
console.log(this)
Running the JavaScript in a browser, will give us something like:
Window {parent: global, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
We see that this refers to the window object, but that’s not always the case.
this is actually a property of the execution context. Okay, so what the heck is an execution context?
An execution context is a description of the environment the code is running in. Among other things, it has a pointer to the value of this and knows which functions and variables are in scope.
Our JavaScript code always runs in an execution context. Before any code is executed, the JavaScript engine sets up a global execution context for us:
The value of this can change when we enter a new execution context. We enter a new execution context when we invoke a function. Lets start with a simple example:
Calling a function as a function
function giveName() {this.name = "Michael"}giveName()console.log(window.name)console.log(this.name)console.log(name)
This output is:
MichaelMichaelMichael
Let’s see what’s going on in terms of the execution context. When the code runs, the JavaScript engine has already set up an execution context for us. This is known as the global execution context (GEC).
A new execution context is created when we invoke giveName
:
Inside giveName
, we modify this by setting a name property on it. We can see that this references the window
object.
When we’re done executing giveName
, we pop the giveName execution context and return to the global execution context.
It should be no surprise now that all the log statements print Michael.
Calling a function as a method
In object-oriented programming languages you might think of a method as a function that belongs to a class. In JavaScript however, a method is a function, that belongs to an object.
Let’s see an example:
const spiderman = {shootWeb: function() {console.log("🕸️")},getThis: function() {return this},}console.log(spiderman === spiderman.getThis())
The above program outputs true. The crucial part here is that getThis is a property on spiderman, so invoking it results in this being set to the object on the left side of the dot, i.e. spiderman.
The program starts out in the global execution context:
When we enter the execution context of the spiderman.getThis()
call, we see that this now points to the
target of the method invocation, namely the spiderman object:
We pop the spiderman.getThis()
execution context when we return from the function.
The function getThis
returns the value pointed to by this while it was invoked. In our case this pointed to spiderman and we
therefore see true in our output.
Calling a function as a constructor
As you’re about to see, there’s nothing special about a constructor function.
The only noticeable difference is that it starts with a capital letter, but this is just a convention.
It’s a good convention though, so stick to it! What’s special is the way we invoke it, using the new
keyword.
In the next example we use the new
keyword to invoke the Superhero
function as a constructor.
This has a few implications:
- A new object is created
- The value of this is bound to the new object during execution of the function
- The object created in step 1 is returned as a result of the whole new expression, unless the function explicitly returns another object.
function Superhero() {this.job = "fight crime"}const wonderwoman = new Superhero()console.log(wonderwoman.job)
The program outputs: fight crime.
As with any other JavaScript program, we start in the global execution context:
When we enter the execution context of Superhero, by using the new
operator, this points to a newly created object:
We then assign a value to this.job
, which sets the job property on the new object:
Upon returning to the global execution context, we save the new object returned from the new-expression in a variable called wonderwoman.
What if we forget new
It’s easy to forget those three letters, n-e-w, and this frequently happens. Let’s see what happens if we forget it.
function Superhero() {this.job = "fight crime"}const wonderwoman = Superhero() // Oh no! Forgot to add new 🤦console.log(wonderwoman.job)
We get an error:
Uncaught TypeError: Cannot read property 'job' of undefinedat <anonymous>:6:25
Invoking Superhero no longer returns an object, because we’re not invoking it as a constructor, but rather as a function.
So Superhero()
returns undefined, hence the TypeError.
But what does this point to? Recall that if we invoke a function as a function, this points to window. So we’re actually setting a property on window, and if we were to log it, we would get the proof:
console.log(window.job)
This outputs: fight crime. It is now the windows job to fight crime and wonderwoman is undefined, how will we ever survive? 😮 What’s so bad about this is that it fails silently. We do not discover the issue until we try to access the job property on wonderwoman.
We can make this a great deal better by adding “use strict” to our program.
"use strict"function Superhero() {this.job = "fight crime"}const wonderwoman = Superhero() // Oh no! Forgot to add new 🤦
When in strict mode, invoking a function as a function sets this to undefined. This is great because we don’t accidentally add properties to the global object and we catch the error earlier. The above program fails during the excution of Superhero.
Uncaught TypeError: Cannot set property 'job' of undefinedat Superhero (<anonymous>:4:12)
This is just one of many things fixed by “use strict”, and I advise you to always use strict mode.
Calling a function via apply or call
The last way to invoke a function in JavaScript, is used when you want to be explicit about the value of this.
You do that by using call
or apply
. They are very similar; the only difference is that apply
accepts its arguments as an array, while call
takes each argument separately.
// Invoke myFunc with arg1 and arg2, but with 'this' set to thisArgmyFunc.call(thisArg, arg1, arg2) // With callmyFunc.apply(thisArg, [arg1, arg2]) // With apply
One usecase for call
or apply
is to chain constructors:
function Media(path) {this.path = path}function Sound(path) {Media.call(this, path)this.format = "mp3"this.bitrate = 320}function Video(path) {Media.call(this, path)this.format = "mp4"this.fps = 30}const sound = new Sound("assets/sounds/woof.mp3")const video = new Video("assets/video/doggo.mp4")
I think you know what will happen if you try to invoke Media as a function: Media(path)
.
The use of call
allow us to extend the new object created when we invoke Video
or Sound
as a constructor. An object created with the Sound constructor will contain not only the properties format and bitrate, but also path.
Conclusion
I hope you now know what this is or at least how to figure it out. It really just boils down to how functions are invoked. I didn’t mention arrow functions, because they honestly deserve their own blog post. Let me end this post with a little challenge. Look at the following code:
const myObject = {logThis: function() {console.log(this)},}const logThis = myObject.logThislogThis()
What does it log? How do you change it with call
or apply
? Try to see if you can use bind instead.