What if I tell you that this in JS is simple?
What if I tell you that there is no magic and you can come up with it in a few minutes?
What if I tell you that there are simple 5-step algorithms that will help you quickly and definitively solve any this-related task in JS and feel confident when working with it?
Can’t believe it? Look, here it is:
this is an implicit parameter, not magic
When you define a function, you define which parameters it must expect. You define explicit parameters. For example, explicit parameter smth in function logSmth(smth) {console.log(smth)}.
this is very same to it, but it is an implicit parameter. It means that the engine passes it itself, even though you don’t declare it.
In case with explicit parameters You decide what to pass, and in case with implicit parameters (including this) the Engine decides itself, ruling by its own rules STEP BY STEP.
What are the rules?
To decide what to pass inside your function under this value JS engine uses 5 simple conditions, 5 simple questions, step by step:
1. Is the function an arrow function?
If your function is arrow one, then the engine does not pass this at all, and when you try to access this from within an arrow function, then the engine looks for it through closure, same as any other variable.
Example:
const variableOutsideArrowFunction = 67;
console.log(variableOutsideArrowFunction);
console.log(this);
const arrowFunction = () => {
console.log(variableOutsideArrowFunction); // exactly same as on line 3
console.log(this); // exactly same as on line 3
};
arrowFunction();
If you try to access this from within an arrow function, then it does not matter whether it is called as a method, whether strict mode is enabled, or even if the function was modified by .bind. If you try to access this from within an arrow function, then the engine will always look for it through the function’s closure, same as a plain variable.
2. Was the function modified by .bind, .call, or .apply?
You can’t explicitly specify the value of this during the creation of the function. But you can create a new, modified one using .bind with an explicitly specified value of this, or you can call a function using .call or .apply, also explicitly specifying what to use as this.
So if you try to access this from within a normal (not arrow) function and you modified it by .bind , .call, or .apply, then this will be exactly what you have specified.
3. Was the function called as a constructor after new?
If you try to access this from within a normal function that was not modified by .bind and your function IS called as a constructor right after new (for example, new User()), then inside this will be that newly created object.
4. Was the function called as an object’s method after a dot?
If you try to access this from within a normal function that was not modified by you and your function is NOT called as a constructor after new, but IS called as an object’s method (after a dot), then this will be the object before the dot.
5. Is “use strict” active?
That is the last condition.
If you try to access this from within a normal function.
And that function is NOT modified by .bind.
And that function is NOT called as a constructor after new and is NOT called as an object’s method.
The final decision that the engine will make depends on whether “use strict” is enabled.
If “use strict” is enabled, then the engine will pass undefined as the value of this, and if “use strict” is not enabled, then the engine will pass the global object (window) as the value of this.
That is it. The end.
Congrats🥳
Now you know everything you really need know about this!
Examples for consolidation
Example 1.1. Example 1.2. Example 1.3. Example 1.4. Explanation of example 1.1. In both cases, we have normal, not an arrow function. In both cases, those functions don’t have the “pinned” value of Explanation of example 1.2. We have normal, not an arrow function. That function doesn’t have the “pinned” value of Explanation of example 1.3. In both cases, we have normal, not an arrow function. In both cases, those functions don’t have the “pinned” value of Explanation of example 1.4. In both cases, we have normal, not an arrow function. In both cases, those functions don’t have the “pinned” value of Example 2.1. Example 2.2. Example 2.3. Example 2.4. Explanation of example 2.1. Function is not an arrow, but normal. Not Explanation of example 2.2. Functions are not arrow, but normal. Not Explanation of example 2.3. Function is not an arrow, but normal. Not Explanation of example 2.4. Function is not an arrow, but normal. Not Example 3.1. Errors in two places (where I commented code) will be because it is forbidden to use constructors in that way. And results and reasons in other places I think are obvious. Example 4.1. Example 4.2. Example 4.3. Explanation of example 4.1. Functions are arrow. Again. Functions are arrow. So it Explanation of example 4.2. In both cases, there will be errors. In the first case, because an arrow function can’t be a constructor. And in the second case, there is a syntax error. Explanation of example 4.3. Here you must pay really huge attention! Nuances of how closures, scope, and lexical environment work are a different interesting topic. Left comment and subscribe if you are interested in and I’ll try to explain that topic as well.Click to unfold them
Normal separate function in sloppy vs strict mode
const func1 = function () {
console.log(this);
};
function func2() {
console.log(this);
}
func1();
func2();
function wrapper() {
function func() {
console.log(this);
}
func();
}
wrapper();
"use strict";
const func1 = function () {
console.log(this);
};
function func2() {
console.log(this);
}
func1();
func2();
const sloppyFunction = function () {
console.log(this);
};
const strictFunction = function () {
"use strict";
sloppyFunction();
console.log(this);
};
strictFunction();
this explicitly. In both cases, functions are called as plain functions, not as constructors or methods. And in both cases, there are sloppy, not strict modes. So in both cases you will see window in the console.this explicitly. The function is called a plain function, not a constructor or method. Regardless of whether it was wrapped with another function, that has no sense and impacts on nothing. And of course you see window in the console.this explicitly. In both cases, functions are called as plain functions, not as constructors or methods.
And because we have strict mode enabled, this is undefined, not window.this explicitly. In both cases, functions are called as plain functions, not as constructors or methods.
But the code of the first function runs in sloppy mode, and the code of the second function runs in strict mode.
Constructor vs Object's method vs Separate function
const object = {
logThis: function () {
console.log(this);
console.log(this === object);
},
};
object.logThis();
const object = {
logThis() {
console.log(this);
console.log(this === object);
},
};
object.logThis();
const object = {};
const logger = function () {
console.log(this);
console.log(this === object);
};
object.logThis = logger;
object.logThis();
const object = {};
object.logThis = function () {
console.log(this);
console.log(this === object);
};
object.logThis();
const Constructor = function () {
this.a = "a";
this.b = "b";
console.log(this);
};
const instance = new Constructor();
class SomeClass {
constructor() {
this.a = "a";
this.b = "b";
console.log(this);
}
}
const instance = new SomeClass();
const Constructor = function () {
this.modifyAndLogThis = 67;
console.log(this);
};
const object = {
modifyAndLogThis: Constructor,
};
object.modifyAndLogThis();
object.modifyAndLogThis();
const SloppyConstructor = function () {
this.randomVal = Math.random();
console.log(this);
};
const StrictConstructor = function () {
"use strict";
this.randomVal = Math.random();
console.log(this);
};
const sloppyFuncAsVariable = SloppyConstructor;
const strictFuncAsVariable = StrictConstructor;
SloppyConstructor();
console.log(window.randomVal);
sloppyFuncAsVariable();
console.log(window.randomVal);
strictFuncAsVariable();
StrictConstructor();
.bind, not .call/.apply was not used. The function was not called as a constructor after new, but it was called as a method. And console.log(this) will log that object. And console.log(this === object) will be true..bind, not .call/.apply was not used. But as functions are called after the keyword new (it means as constructors), then the value of this is that object..bind, not .call/.apply was not used.
Even though we declared the function starting with a capital letter and planned to use it as a constructor, we didn’t use it as a constructor. We move that function inside the object and call it as the object’s method. And then during the first function’s call, this was that object, and the function replaced modifyAndLogThis with 67 . And then during the second call object.modifyAndLogThis was 67, and 67 is a number, not a function, so you see an error in the console..bind, not .call/.apply was not used.
Even though we declared the function starting with a capital letter and planned to use it as a constructor, we didn’t use it as a constructor. We called them as plain separate functions.
.bind, .call, .apply
const sloppyFunc = function () {
console.log(this);
};
const strictFunc = function () {
"use strict";
console.log(this);
};
const Constructor = function () {
this.a = "a";
console.log(this);
};
const object = {
logThis() {
console.log(this);
},
};
// normal calls
sloppyFunc();
strictFunc();
Constructor();
new Constructor();
object.logThis();
// explicitly passed values
sloppyFunc.apply({ bim: "bam" });
strictFunc.apply({ bim: "bam" });
Constructor.apply({ bim: "bam" });
// new Constructor.apply({bim: "bam"}); // error
object.logThis.apply({ bim: "bam" });
sloppyFunc.bind({ bom: "bem" })();
strictFunc.bind({ bom: "bem" })();
Constructor.bind({ bom: "bem" })();
// new Constructor.bind({bom: "bem"})(); // error
object.logThis.bind({ bom: "bem" })();
An arrow functions
console.log(this);
const sloppyArrowFunc = () => {
console.log(this);
};
sloppyArrowFunc();
const strictArrowFunc = () => {
"use strict";
console.log(this);
};
strictArrowFunc();
const object1 = {
sloppyLogThis: sloppyArrowFunc,
strictLogThis: strictArrowFunc,
};
object1.sloppyLogThis();
object1.strictLogThis();
const object2 = {
sloppyLogThis: () => {
console.log(this);
},
strictLogThis: () => {
console.log(this);
},
};
object2.sloppyLogThis();
object2.strictLogThis();
const basicArrowFunc = () => console.log(this);
basicArrowFunc.apply({});
const arrowFuncWithBindedThis = basicArrowFunc.bind({});
arrowFuncWithBindedThis();
const wrapper = {
hello: "world",
executeExperiment: function () {
console.log(this, wrapper === this);
const sloppyArrowFunc = () => {
console.log(this, wrapper === this);
};
sloppyArrowFunc();
const strictArrowFunc = () => {
"use strict";
console.log(this, wrapper === this);
};
strictArrowFunc();
const object1 = {
sloppyLogThis: sloppyArrowFunc,
strictLogThis: strictArrowFunc,
};
object1.sloppyLogThis();
object1.strictLogThis();
const object2 = {
sloppyLogThis: () => {
console.log(this, wrapper === this);
},
strictLogThis: () => {
console.log(this, wrapper === this);
},
};
object2.sloppyLogThis();
object2.strictLogThis();
const basicArrowFunc = () => console.log(this, wrapper === this);
basicArrowFunc.apply({});
const arrowFuncWithBindedThis = basicArrowFunc.bind({});
arrowFuncWithBindedThis();
},
};
wrapper.executeExperiment();
const ArrowConstructor = () => {
this.a = "a";
};
const instance = new ArrowConstructor();
class SomeClass {
constructor: () => {
this.a = "a";
}
}
const instance = new SomeClass();
This one is the most interesting!
function buildArrowFunc() {
const randomNumber = Math.random();
return () => console.log(this, randomNumber);
}
const object = {
buildArrowFunc, // short syntax for "buildArrowFunc: buildArrowFunc,"
};
const arrowFunction1 = buildArrowFunc();
const arrowFunction2 = object.buildArrowFunc();
console.log(buildArrowFunc === object.buildArrowFunc);
arrowFunction1();
arrowFunction2();
.bind/.call/.apply don’t matter. Whether they are methods or not doesn’t matter. Whether they are constructors matters because we can’t use an arrow function as a constructor, but anyway. And whether there is sloppy mode or strict mode, more so doesn’t matter.
Really matters that they are arrow functions and the engine will look for this within the closure same as for any other variable.arrowFunction1 is an arrow one, and arrowFunction2 is an arrow one. And of course, in both cases, the engine looks for the value of this within the closure, just as it looks for randomNumber. But those functions have different closures. That is why the values of this and of randomNumber are different.
Cheatsheet:
- Is it an Arrow function?
- if Yes, then the engine looks for
thisthrough closure, the same as for plain variables. - if No, then was the function modified by
.bind,.call, or.apply? - if Yes, then the engine passes that explicitly provided object as the value of
this. - if No, then was the function called as a constructor after
new?- if Yes, then the engine passes the newly created object as the value of
this - if No, then was function called as object’s method after dot?
- if Yes, then the engine passes the object before the dot as the value of
this. - is 'use strict' active?
- if Yes, then the engine passes
undefinedas the value ofthis. - if No, then the engine passes global object (
window) as the value ofthis.
- if Yes, then the engine passes
- if Yes, then the engine passes the newly created object as the value of
- if Yes, then the engine looks for
Isn't it simple? 🤓







Top comments (0)