this within TypeScript/JavaScript is not the same as you’re used to if you come from an OO language such as C#, Java, C++ or the likes.
Within these languages this is the internal reference to the instance of the object your methods are members of, within TypeScript/JavaScript this depends upon the current “execution context”. This is one of the reasons when writing code for event handler in React (for example) we need code such as
this.handleClick = this.handleClick.bind(this);
In JavaScript the runtime maintains a stack of execution contexts. As such functions (not part of a class) have access to this which will point to the global object, which in a browser would be window and via node is a special global object.
Yes for those coming from an OO language it would seem odd that functions outside of classes have access to this.
For example, running node index.js on this index.js file, will display Object [global], however if we add ‘use strict’; to the start of index.js this will be undefined.
function fn() {
console.log(this);
}
fn();
If we now create a JavaScript class (either ECMAScript 2015) for example
class MyClass {
constructor(arg1, arg2) {
this.arg1 = arg1;
this.arg2 = arg2;
}
}
We’ll find that this no longer references the global object, instead the new keyword (when we create an instance of this class) causes the JavaScript runtime to create a new object assigned to this specific to the class. Hence if you console.log(this) from the class you’ll see this within a new execution context, scoped to the class.
Let’s return to our class and add a new method, so the MyClass code should look like this
class MyClass {
constructor(arg1, arg2) {
this.arg1 = arg1;
this.arg2 = arg2;
}
output() {
console.log(this);
}
}
if we now execute the following
const mc = new MyClass("Scooby", "Doo");
mc.output();
as you’d expect, the out function logs the MyClass instance (shown below) to the console.
MyClass { arg1: 'Scooby', arg2: 'Doo' }
If, however we instead have the following code
const mc = new MyClass("Scooby", "Doo");
const fn = mc.output;
fn();
the fn() call, which is ultimately just a call mc.output() will output undefined for this. What’s happening is we’re actually executing the function outside of the class and this (in node) is now undefined.
We might think that C# etc. is wrapping this in a closure or the likes, whereas JavaScript is not. So how do we “bind” this to the new output function – the clue is in the use of the work “bind”. Adding the following to the constructor, now binds the this from the class to the method and now calling fn() will output the MyClass object.
this.output = this.output.bind(this);
Interestingly, as JavaScript function have a bind, call and apply functions on them, we could actually bind the function to a totally different instance of an object, hence if we created a totally different class and then bind the function to it, the output will display the new object, i.e.
class PointlessClass {
}
and now in the MyClass constructor we have
this.output = this.output.bind(new PointlessClass());
Then, using either method of calling the output method or assigning to fn and calling outside of the class, we’ll get PointlessClass {} logged to the console.
If we go back to the code
const mc = new MyClass("Scooby", "Doo");
const fn = mc.output;
fn();
We can change this to bind (instead of the constructor) if we so wished, for example
const mc = new MyClass("Scooby", "Doo");
const fn = mc.output.bind(mc);
fn();
The above will now output the instance of MyClass as it’s this.
As previously mentioned a function can bind, call and apply. So bind sets the this on the method and every time the method is called it’s bound to MyClass. Call executes a method against the supplied this only for that single call as does apply i.e.
const fn = mc.output;
fn.call(mc);
fn();
the fn.call will output an instance of MyClass but the fn() will again output undefined (or the global execution context).
I mentioned call and apply did the same things, so what’s the difference? The difference is in the way arguments are passed to these functions, call takes an array of arguments whereas apply takes a set, other than that they do the same thing, immediately execute the method against the supplied this.
Finally, arrow (also known as fat arrow) functions are part ECMAScript 2015 and they are automatically bound to this, let’s assume we change our MyClass to
class MyClass {
constructor(arg1, arg2) {
this.arg1 = arg1;
this.arg2 = arg2;
this.output = () => console.log(this);
}
}
Now if we use this function like we did earlier, which if you recall output undefined for this
const fn = mc.output;
fn();
with the arrow function we found this was set to the instance of MyClass it was declared within. So basically it appears to be already bound, in reality it’s better to think of the arrow functions as inheriting the this because unlike non arrow methods, we cannot rebind it’s this reference. For example, the following will still output the MyClass instance, not the new PointlessClass that we’ve attempt to bind to
const fn = mc.output.bind(new PointlessClass());
fn();
[/code[