Understanding the rules of the "this" keyword in JavaScript
Fortune Ekeruo
To properly understand how the this
keyword works in JavaScript you first need to understand its rules, and in this post I’ll go over them with some examples.
For better explanation, I've written most of the example code in ES5.
Rule #1
When an object is called with a new
keyword the value of this
becomes an empty JavaScript object ({}
)
// Rule #1 example
function RuleOne() {
this.value = 'Some value for rule #1';
this.printValue = function () {
console.log('Rule one value >>>>', this.value);
};
}
// using the new keyword here creates a brand new object for "this"
const ruleOneInstance = new RuleOne();
ruleOneInstance.printValue();
// without the "new" keyword here, no new object is created for "this"
// and whatever "this" value is has no "printValue" method
const anotherRuleOneInstance = RuleOne();
anotherRuleOneInstance.printValue(); //->TypeError:Cannot read property 'printValue' of undefined
In the example above, I created a class function with a constructor that initializes a property called value
and a method called printValue
.
When the instance of the RuleOne
class is created with the new
keyword the value of the this
becomes an empty object ({}
) thereby allowing us to set the initializations (value
and printValue
) and when we call the method printValue we get the expected output.
But when the instance is created without the new
keyword the value of this
is the global this
value where the instance is created.
Rule #2
When a method/function is called with bind
, apply
or call
the value of this
becomes the argument object that was passed to them.
// Rule #2 example
function RuleTwo() {
this.value = 'Some value for rule #2';
this.printValue = function () {
console.log('Rule two value >>>>', this.value);
};
}
const ruleTwoInstance = new RuleTwo();
// Original value of 'this' used here
ruleTwoInstance.printValue();
// Bounded value is used here
ruleTwoInstance.printValue.bind({ value: 'Bound value' })();
In the example above I used bind
to alter the value of this
which is passed as an object into bind
and invoked afterward. This can also be done with apply
or call
.
Rule #3
When a method/function is called with a dot (.
) notation the value of this
becomes the caller object by the left, as in person.sing()
here, the value of this
to sing()
becomes the person
object.
// Rule #3 example
function RuleThree() {
this.value = 'Some value for rule #3';
this.printValue = function () {
console.log('Rule three value >>>>', this.value);
};
}
const ruleThreeInstance = new RuleThree();
const ruleThreePrintValue = ruleThreeInstance.printValue;
ruleThreePrintValue();
The value of this
passed to printValue
is the ruleThreeInstance
object.
Rule #4
When a function is called as a free function invocation (FFI) the value of this
becomes the global object (ie. the window
object in a browser environment or the process
object in a node server environment)
// Rule #4 example
this.value = 'Some value';
function ruleFour() {
console.log('Rule four value >>>>', this.value);
}
// Refers to the global "this" value
ruleFour();
Rule #5
When two or more of these rules apply, the higher rule takes precedence.
// Rule #5 example
function RuleFive() {
this.value = 'Some value for rule #5';
this.printValue = function () {
console.log('Rule five value >>>>', this.value);
};
}
const ruleFiveInstance = new RuleFive();
/**
* Rule #2 call method takes precedence here
* even though dot notation of rule #3 applies
*/
ruleFiveInstance.printValue.call({ value: 'Another bounded val' });
// Rule #1 new keyword takes precedence even tho rule #3 applies
new ruleFiveInstance.printValue();
Rule #6
The birth of ES6 arrow functions introduced the 6th rule which is:
When a method/function is defined with an arrow function it copies the value of this
into its immediate surrounding scope ignoring all of the rules above.
// Rule #6 example (arrow functions)
function RuleSix() {
this.value = 'Some value for rule #6';
this.printValue = () => {
console.log('Rule six value >>>>', this.value);
};
}
const ruleSix = new RuleSix();
const printRuleSixVal = ruleSix.printValue;
// the this of parent scope is still in use unlike in RuleThree eg
printRuleSixVal();
Conclusion
Thanks for reading, if you find anything confusing or need me to clarify/correct something please send me a tweet via @fortune_ik