DEV Community

Andrii Hrushetskyi
Andrii Hrushetskyi

Posted on

"this" in JS is SIMPLE as a rock

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();
Enter fullscreen mode Exit fullscreen mode

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.

step-1-is-function-an-arrow-function

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.

step-2-was-function-modified-by-bind-or-call-or-apply-methods

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.

step-3-was-function-called-as-constructor-after-new-keyword

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.

step-4-was-function-called-as-method-of-array-after-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.

step-5-is-strict-mode-enabled

That is it. The end.

Congrats🥳

Mission completed

Now you know everything you really need know about this!


Examples for consolidation

Click to unfold them

Normal separate function in sloppy vs strict mode

Example 1.1.

const func1 = function () {
  console.log(this);
};

function func2() {
  console.log(this);
}

func1();
func2();
Enter fullscreen mode Exit fullscreen mode

Example 1.2.

function wrapper() {
  function func() {
    console.log(this);
  }

  func();
}

wrapper();
Enter fullscreen mode Exit fullscreen mode

Example 1.3.

"use strict";

const func1 = function () {
  console.log(this);
};

function func2() {
  console.log(this);
}

func1();
func2();
Enter fullscreen mode Exit fullscreen mode

Example 1.4.

const sloppyFunction = function () {
  console.log(this);
};

const strictFunction = function () {
  "use strict";

  sloppyFunction();
  console.log(this);
};

strictFunction();
Enter fullscreen mode Exit fullscreen mode

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 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.

Explanation of example 1.2. We have normal, not an arrow function. That function doesn’t have the “pinned” value of 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.

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 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.

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 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

Example 2.1.

const object = {
  logThis: function () {
    console.log(this);
    console.log(this === object);
  },
};
object.logThis();
Enter fullscreen mode Exit fullscreen mode
const object = {
  logThis() {
    console.log(this);
    console.log(this === object);
  },
};
object.logThis();
Enter fullscreen mode Exit fullscreen mode
const object = {};
const logger = function () {
  console.log(this);
  console.log(this === object);
};
object.logThis = logger;
object.logThis();
Enter fullscreen mode Exit fullscreen mode
const object = {};
object.logThis = function () {
  console.log(this);
  console.log(this === object);
};
object.logThis();
Enter fullscreen mode Exit fullscreen mode

Example 2.2.

const Constructor = function () {
  this.a = "a";
  this.b = "b";
  console.log(this);
};

const instance = new Constructor();
Enter fullscreen mode Exit fullscreen mode
class SomeClass {
  constructor() {
    this.a = "a";
    this.b = "b";
    console.log(this);
  }
}

const instance = new SomeClass();
Enter fullscreen mode Exit fullscreen mode

Example 2.3.

const Constructor = function () {
  this.modifyAndLogThis = 67;
  console.log(this);
};

const object = {
  modifyAndLogThis: Constructor,
};

object.modifyAndLogThis();
object.modifyAndLogThis();
Enter fullscreen mode Exit fullscreen mode

Example 2.4.

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();
Enter fullscreen mode Exit fullscreen mode

Explanation of example 2.1. Function is not an arrow, but normal. Not .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.

Explanation of example 2.2. Functions are not arrow, but normal. Not .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.

Explanation of example 2.3. Function is not an arrow, but normal. Not .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.

Explanation of example 2.4. Function is not an arrow, but normal. Not .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

Example 3.1.

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" })();
Enter fullscreen mode Exit fullscreen mode

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.

An arrow functions

Example 4.1.

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();
Enter fullscreen mode Exit fullscreen mode
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();
Enter fullscreen mode Exit fullscreen mode

Example 4.2.

const ArrowConstructor = () => {
  this.a = "a";
};

const instance = new ArrowConstructor();

class SomeClass {
  constructor: () => {
    this.a = "a";
  }
}

const instance = new SomeClass();
Enter fullscreen mode Exit fullscreen mode

Example 4.3.
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();
Enter fullscreen mode Exit fullscreen mode

Explanation of example 4.1. Functions are arrow. Again. Functions are arrow. So it .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.

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! 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.

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.

Cheatsheet:

  • Is it an Arrow function?
    • if Yes, then the engine looks for this through 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 undefined as the value of this.
        • if No, then the engine passes global object (window) as the value of this.

full-schema-about-this-in-javascript-5-simple-steps


Isn't it simple? 🤓

Top comments (0)