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