How [this] happened
August 04, 2021
| Content |
|---|
| Binding of [this] established at runtime, when the function is invoked |
| What determines [this] is how the function is invoked. |
| Where the function is defined is irrelevant in determining [this] value. |
| The value of [this] is always an object....although sometimes pure witchcraft |
In JavaScript, the value of is determined by the invocation context of
the
function.this
In the example below, we have declared two variables: size and myObj. We also have an
object with a function method in it - all sharing the same scope - in this case global
scope, and last at the very bottom a function call: myObj.sizeFun().
[This] value here can be either 'medium' or 'large'. Although it is determined at runtime, I know it is 'large', and the fact that it was call from outside of myObj, it doesn't matter. What matters here is that myObj is an object and the function called is a property/method of myObj...otherwise the scope had been global and the value 'medium' instead of 'large'.
//value of [this] window scope.
var size = 'small';
//this crazy
this.size = 'medium';
var myObj = { //parent object
size: 'large',
sizeFun: function() {//function method
console.log(this.size);
}
}
//[this] value determined at runtime
myObj.sizeFun();
Value of [this] by invocation
In the first three function calls below, this binding is implicit - set by the JavaScript engine. The last type uses call and apply during the function invocation to have some say over its binding, thus - explicitly set by the user.
1 - Normal function invocation 2 - Function as a method attached to an object 3 - Function constructor using new keyword 4 - Function call with either call() and apply()
Call, Apply and Bind
These 3 methods allows you to manipulate the value of [this] regardless of the context.
const todo = [{
day: 'Monday',
do: 'play video games',
}]
function whatDoYouDo() {
console.log(`On ${this[0].day} I usually ${this[0].do}.`)
}
whatDoYouDo.bind(todo)() or whatDoYouDo.call(todo) or whatDoYouDo.apply(todo)
On Monday I usually play video games.
//Removing the array on todo, you'll have this
function whatDoYouDo() {
console.log(`On ${this.day} I usually ${this.do}.`)
}
Call and Apply both take multiple arguments. Unlike the call method, if you need to pass additional arguments, with apply you pass an array. Bind's normally used for holding on/retaining to the value of [this]. For example, a function that's called multiple times at runtime. So what you do is create a handle to the this binding.
let mondayDo = whatDoYouDo.bind(todo)
//or like it had to be be done with React in the past to bind a class method
this.getName = this.getName.bind(this);
Normal function invocation
Below, we have a declared function, and we want to know what the value of this is inside of it. Next, proceed to call fun1; which sets the value of this to the global scope, and the name variable to global.
var name = 'global';
var fun1 = function() {
var name = "fun1";
console.log(this);
console.log(this.name);
}
fun1();
Calling a function from another
A function call from inside of another function. We first defined both functions, and proceed to invoke fun1. Fun1 is executed and fun2 then called from within fun1. Despite calling fun2 from within fun1, it still returns the same window scope for this and global type string value for name.
var name = 'global';
var fun1 = function() {
var name = "fun1";
console.log(this);
console.log(this.name);
fun2();
}
var fun2 = function() {
var name = "fun2";
console.log(this);
console.log(this.name);
}
fun1();
//same as
this.fun1()
Returning a function call
Returning a function from a function call. Here, we declared a function and a variable. Fun1 is set to return an anonymous function this time, and we want to find out the value of this inside of it. To call the anonymous function, instead of just returning the function, we first assign fn1 invocation to a new variable x.
var name = 'global';
var fun1 = function() {
var name = "fun1";
console.log("From fun1---");
console.log(this);
console.log(this.name);
return function() {
var name = "fun2";
console.log("From fun2---");
console.log(this);
console.log(this.name);
}
}
var x = fun1();
x();
Defining the function in this manner did not change the binding of this during the function call, and much less the value of global.
Passing function as an argument in another function
First-class functions can be treated as values, and pass as arguments of another function.
Here fun1 and fun2 are defined along with the name variable. Fun2 passed in as an argument in fun1.
var name = 'global';
var fun1 = function(fn) {
var name = "fun1";
console.log(this);
console.log(this.name);
fn();
}
function fn2() {
var name = "fun2";
console.log(this);
console.log(this.name);
}
fun1(fn2);
Just as before, the value of this is the window scope and the value of name is global. When invoking a normal function, the value of this is the global scope.
Function invocation - Strict mode
In strict mode, it works differently. It doesn't work at all. A normal function invocation in strict mode below.
'use strict';
var name = 'global'
var fun0 = function() {
var name = "local";
console.log(this);
console.log(this.name);
}
fun0;
In strict mode, the global object is no longer available for binding, and the value of this inside of fn0 is undefined and the name variable now, it'll return an error.
Object - method invocation
Again this binding determined by the method call (parent object-dot-method).
Calls the method fn1 of obj1.
var name = 'global';
var obj1 = {
name: 'local',
fun1: function() {
console.log(this);
console.log(this.name);
}
}
obj1.fun1();