DEV Community

Cover image for Finally Understanding the Advanced uses of "This" in Javascript
bricourse
bricourse

Posted on

Finally Understanding the Advanced uses of "This" in Javascript

In this short tutorial we are going to learn how to use "this" in Javascript with 7 different examples …


If the function is defined as an arrow function: {: #arrow-functions }

const arrowFunction = () => {
  console.log(this);
};
Enter fullscreen mode Exit fullscreen mode

In this case, the value of this is always the same as this in the parent scope:

const outerThis = this;

const arrowFunction = () => {
  // Always logs `true`:
  console.log(this === outerThis);
};
Enter fullscreen mode Exit fullscreen mode

Arrow functions are great because the inner value of this can't be changed, it's always the same
as the outer this.

Other examples

With arrow functions, the value of this can't be changed with bind:

// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
Enter fullscreen mode Exit fullscreen mode

With arrow functions, the value of this can't be changed with call or apply:

// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
Enter fullscreen mode Exit fullscreen mode

With arrow functions, the value of this can't be changed by calling the function as a member of
another object:

const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
Enter fullscreen mode Exit fullscreen mode

With arrow functions, the value of this can't be changed by calling the function as a
constructor:

// TypeError: arrowFunction is not a constructor
new arrowFunction();
Enter fullscreen mode Exit fullscreen mode

'Bound' instance methods

With instance methods, if you want to ensure this always refers to the class instance, the best
way is to use arrow functions and class
fields
:

class Whatever {
  someMethod = () => {
    // Always the instance of Whatever:
    console.log(this);
  };
}
Enter fullscreen mode Exit fullscreen mode

This pattern is really useful when using instance methods as event listeners in components (such as
React components, or web components).

The above might feel like it's breaking the "this will be the same as this in the parent scope"
rule, but it starts to make sense if you think of class fields as syntactic sugar for setting things
in the constructor:

class Whatever {
  someMethod = (() => {
    const outerThis = this;
    return () => {
      // Always logs `true`:
      console.log(this === outerThis);
    };
  })();
}

// …is roughly equivalent to:

class Whatever {
  constructor() {
    const outerThis = this;
    this.someMethod = () => {
      // Always logs `true`:
      console.log(this === outerThis);
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Alternative pattens involve binding an existing function in the constructor, or assigning the
function in the constructor. If you can't use class fields for some reason, assigning functions in
the constructor is a reasonable alternative:

class Whatever {
  constructor() {
    this.someMethod = () => {
      // …
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Otherwise, if the function/class is called with new: {: #new }

new Whatever();
Enter fullscreen mode Exit fullscreen mode

The above will call Whatever (or its constructor function if it's a class) with this set to the
result of Object.create(Whatever.prototype).

class MyClass {
  constructor() {
    console.log(
      this.constructor === Object.create(MyClass.prototype).constructor,
    );
  }
}

// Logs `true`:
new MyClass();
Enter fullscreen mode Exit fullscreen mode

The same is true for older-style constructors:

function MyClass() {
  console.log(
    this.constructor === Object.create(MyClass.prototype).constructor,
  );
}

// Logs `true`:
new MyClass();
Enter fullscreen mode Exit fullscreen mode

Other examples

When called with new, the value of this can't be changed with bind:

const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
Enter fullscreen mode Exit fullscreen mode

When called with new, the value of this can't be changed by calling the function as a member
of another object:

const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
Enter fullscreen mode Exit fullscreen mode

Otherwise, if the function has a 'bound' this value: {: #bound }

function someFunction() {
  return this;
}

const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
Enter fullscreen mode Exit fullscreen mode

Whenever boundFunction is called, its this value will be the object passed to bind
(boundObject).

// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
Enter fullscreen mode Exit fullscreen mode

Avoid using bind to bind a function to its outer this. Instead, use arrow functions, as they make this clear from the function declaration, rather than
something that happens later in the code.

Don't use bind to set this to some value unrelated to the parent object; it's usually unexpected and it's why this gets such a bad reputation. Consider passing the value as an argument instead; it's more explicit, and works with arrow functions.

Other examples

When calling a bound function, the value of this can't be changed with call or
apply
:

// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
Enter fullscreen mode Exit fullscreen mode

When calling a bound function, the value of this can't be changed by calling the function as a
member of another object:

const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
Enter fullscreen mode Exit fullscreen mode

Otherwise, if this is set at call-time: {: #call-apply }

function someFunction() {
  return this;
}

const someObject = {hello: 'world'};

// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
Enter fullscreen mode Exit fullscreen mode

The value of this is the object passed to call/apply.

Don't use call/apply to set this to some value unrelated to the parent object; it's usually unexpected and it's why this gets such a bad reputation. Consider passing the value as an argument instead; it's more explicit, and works with arrow functions.

Unfortunately this is set to some other value by things like DOM event listeners, and using it can
result in difficult-to-understand code:

element.addEventListener('click', function (event) {
  // Logs `element`, since the DOM spec sets `this` to
  // the element the handler is attached to.
  console.log(this);
});
Enter fullscreen mode Exit fullscreen mode

I avoid using this in cases like above, and instead:

element.addEventListener('click', (event) => {
  // Ideally, grab it from a parent scope:
  console.log(element);
  // But if you can't do that, get it from the event object:
  console.log(event.currentTarget);
});
Enter fullscreen mode Exit fullscreen mode

Otherwise, if the function is called via a parent object (parent.func()): {: #object-member }

const obj = {
  someMethod() {
    return this;
  },
};

// Logs `true`:
console.log(obj.someMethod() === obj);
Enter fullscreen mode Exit fullscreen mode

In this case the function is called as a member of obj, so this will be obj. This happens at
call-time, so the link is broken if the function is called without its parent object, or with a
different parent object:

const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);

const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
Enter fullscreen mode Exit fullscreen mode

someMethod() === obj is false because someMethod isn't called as a member of obj. You might
have encountered this gotcha when trying something like this:

const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
Enter fullscreen mode Exit fullscreen mode

This breaks because the implementation of querySelector looks at its own this value and expects
it to be a DOM node of sorts, and the above breaks that connection. To achieve the above correctly:

const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
Enter fullscreen mode Exit fullscreen mode

Fun fact: Not all APIs use this internally. Console methods like console.log were changed to
avoid this references, so log doesn't need to be bound to console.

Don't transplant a function onto an object just to set this to some value unrelated to the parent object; it's usually unexpected and it's why this gets such a bad reputation. Consider passing the value as an argument instead; it's more explicit, and works with arrow functions.

Otherwise, if the function or parent scope is in strict mode: {: #strict }

function someFunction() {
  'use strict';
  return this;
}

// Logs `true`:
console.log(someFunction() === undefined);
Enter fullscreen mode Exit fullscreen mode

In this case, the value of this is undefined. 'use strict' isn't needed in the function if the parent scope is in strict
mode
(and all modules are in strict mode).

Don't rely on this. I mean, there are easier ways to get an undefined value 😀.

Otherwise: {: #otherwise }

function someFunction() {
  return this;
}

// Logs `true`:
console.log(someFunction() === globalThis);
Enter fullscreen mode Exit fullscreen mode

In this case, the value of this is the same as globalThis.

Most folks (including me) call globalThis the global object, but this isn't 100% technically correct. Here's [Mathias Bynens with the details (https://mathiasbynens.be/notes/globalthis#terminology), including why it's called globalThis rather than simply global.

Avoid using this to reference the global object (yes, I'm still calling it that). Instead, use globalThis,
which is much more explicit.


Additional resources to learn Javascript:

Javascript Challenges

Get the book: Javascript Challenges


JavaScript: Understanding the Weird Parts

Monster JavaScript Course - 50+ projects and applications

reference sites: https://web.dev/javascript-this/

Top comments (10)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
peerreynders profile image
peerreynders • Edited

Try typing following in chrome console.

You made a mistake in the constructor:

class Whatever {
  constructor() {
     this.a = 1; // forgot `this` here 
  }
  someMethod = () => {
    // Now it works
    console.log(this.a);
  };
}

var a1 = new Whatever();
a1.someMethod();
Enter fullscreen mode Exit fullscreen mode

in this case this is a Whatever class.

There are no classes in JavaScript - just objects.

MDN: Classes

Classes are a template for creating objects.

i.e. classes are just a convenience for creating objects in JavaScript - nothing more - objects are not tied to some kind of static class membership.


Your second example isn't surprising:

var w1 =new Whatever();
var w2 = new Whatever2();
console.log(w1.someMethod() === w2.someMethod());  // false
console.log(w1 === w2); // false;
console.log(w1 === w1.someMethod()); // true
console.log(w2 === w2.someMethod()); // true
Enter fullscreen mode Exit fullscreen mode

w1 and w2 are separate objects with different references. So your original comparison will fail just as w1 === w2 will fail.

Meanwhile the reference returned by someMethod is identical to the reference of the object itself.

Collapse
 
akashkava profile image
Akash Kava

Well I was wrong, I learned that this is even available as arrow function that is created inside the class.

Thread Thread
 
peerreynders profile image
peerreynders • Edited

To understand this in arrow functions you need to understand closures/scope.

Because this inside an arrow function refers to whatever this was in the closure at the point in time the arrow function was created.

class Whatever {
  someMethod = (() => {
    const outerThis = this;

    return () => {
      return [outerThis, this];
    };
  })();
}
Enter fullscreen mode Exit fullscreen mode

Dissecting ...

class Whatever {
  someMethod = /* some expression */
}
Enter fullscreen mode Exit fullscreen mode
  • "some expression" runs during the construction of the object so this refers to the object under construction as it is typical for constructor functions.
class Whatever {
  someMethod = (() => {
    // IIFE body
  })();
}
Enter fullscreen mode Exit fullscreen mode
  • in this case the expression is an Immediately Invoked Function Expression (IIFE) - i.e. after the function expression creates the function, the function is immediately executed. All of this still happens inside the constructor function and the return value of that IIFE is assigned to the someMethod property of the object under construction.
class Whatever {
  someMethod = (() => {
    const outerThis = this;

    return () => {
      return [outerThis, this];
    };
  })();
}
Enter fullscreen mode Exit fullscreen mode
  • Inside the IIFE this is copied to outerThis and the IIFE is the closure that creates the function that is assigned to someMethod. But given that all of this is still happening inside the constructor function this is still simply the object under construction.

This may make it more clear what is actually happening:

// Constructor Function
function Whatever() {
  // When invoked by `new`, i.e. `new Whatever()`
  // `new` passes the object under construction
  // as `this` to `Whatever`

  const makeFunction = () => {
    const outerThis = this;

    const builtFunction = () => {
      return [outerThis, this];
    };

    return builtFunction;
  };

  this.someMethod = makeFunction();

  // Note the lack of the a return statement/value
  // It's `new` that returns the constructed object
  // not `Whatever`.
}

const whatever = new Whatever();
const [outer, inner] = whatever.someMethod();

console.assert(outer === inner);
console.assert(outer === whatever);
console.assert(inner === whatever);
Enter fullscreen mode Exit fullscreen mode

Similarly with Function.prototype.call():

function justSomeFunction() {
  const makeFunction = () => {
    const outerThis = this;

    const builtFunction = () => {
      return [outerThis, this];
    };

    return builtFunction;
  };

  this.someMethod = makeFunction();
}

// passing an object literal as `this` with `call`
const obj = {};
justSomeFunction.call(obj);
const [outer, inner] = obj.someMethod();

console.assert(outer === inner);
console.assert(outer === obj);
console.assert(inner === obj);
Enter fullscreen mode Exit fullscreen mode

This is why inside functions this is called the function context. It's the context that is passed to the function so it can do its job. If the function operates as a method the function context refers to the object to which it was invoked on. But in a constructor function the function context refers to the object under construction. So what this is depends entirely on how a function is invoked. Arrow function expressions change the rules - this refers to whatever this was in the creating closure when the arrow function was created. This means that arrow functions on super-types won't work for sub-types because sub-types are unable to pass in their own this into the arrow function.

class A {
  myArrow = () => {
    console.log('A.myArrow()');
  }

  myMethod() {
    console.log('A.myMethod()');
  }
}

class B extends A {
  myArrow = () => {
    super.myArrow(); // arrow function exists on 
                     // public class field of A but
                     // but is not available on 
                     // the prototype chain via `super`
    console.log('B.myArrow()');
  }

  myMix() {
    super.myArrow(); // same problem
    console.log('B.myMix()');
  }

  myMethod() {
    super.myMethod(); // just works
    console.log('B.myMethod()');
  }
} 

let myB = new B();

myB.myMethod(); // 'A.myMethod()'
                // 'B.myMethod()'
myB.myMix();    // Uncaught TypeError: (intermediate value).myArrow is not a function at B.myMix
myB.myArrow();  // Uncaught TypeError: (intermediate value).myArrow is not a function at B.myArrow
Enter fullscreen mode Exit fullscreen mode

For an interesting application of closures see OOP with Functions in JavaScript.

Thread Thread
 
akashkava profile image
Akash Kava

Thanks for clarification, I had assumed that IIFE captured this as class is created in scope of globalThis, but now I understand that fields of class are invoked inside constructor.

Collapse
 
bricourse profile image
bricourse

thanks for the insights and corrections

Collapse
 
bricourse profile image
bricourse

thanks for the insights and corrections

Collapse
 
peerreynders profile image
peerreynders • Edited
this.constructor === Object.create(MyClass.prototype).constructor
Enter fullscreen mode Exit fullscreen mode

All you are demonstrating here is that the left hand side object has the same constructor as the right hand side object - not:

with this set to the result of Object.create(Whatever.prototype).

Given that Object.create will create an entirely new object (i.e. different from the one being prepared by the constructor function) that statement is incorrect.


When called with new, the value of this can't be changed with bind:

bind only works when the function is called as a function - new uses the function as a constructor function - i.e. this is the "construction context" (the new object being constructed) - not the "calling context" (an object providing context for a regular function call).


Don't use bind to set this to some value unrelated to the parent object; it's usually unexpected and it's why this gets such a bad reputation.

The reputation is based on developers educated in class-oriented object orientation being surprised by this not relating to a class instance. They shouldn't be surprised as this is called the function context in JavaScript.

The function context is just often used to emulate method calls (but not always).


EventTarget.addEventListener() actually supports objects directly (since IE 9 (i.e. 2011)).

const handler = {
  handleEvent(e) {
    switch(e.type) {
      case 'click':
        console.log(handler === this); // true
    }
  }
};

document.addEventListener('click', handler);
Enter fullscreen mode Exit fullscreen mode

EventListener.handleEvent()


so the link is broken if the function is called without its parent object, or with a different parent object:

At times it's useful to "borrow" a method.

const obj = {};
const anotherObj = {
  someMethod() {
    return this;
  }
};

console.log(anotherObj.someMethod() === anotherObj); // true
// "borrowing" method from `anotherObj` for `obj` 
console.log(anotherObj.someMethod.call(obj) === obj); // true

/*
  Historical use case:
  var args = Array.prototype.slice.call(arguments);

  More modern use case:

  const hasOwnProperty = Object.prototype.hasOwnProperty;
  hasOwnProperty.call(obj, name);

  to satisfy
  https://eslint.org/docs/rules/no-prototype-builtins
*/
Enter fullscreen mode Exit fullscreen mode

Instead, use globalThis.

Within the browser self can by useful (instead on Window.window) (Window.self and WorkerGlobalScope.self).

Collapse
 
anmshpndy profile image
Animesh Pandey

Good attempt to summarize, with a few issues here and there.

Also take a look at "this", if it helps you or anyone further.
dev.to/anmshpndy/how-well-do-you-k...

Collapse
 
dynamicsquid profile image
DynamicSquid

I finally understand this!