DEV Community

Discussion on: Finally Understanding the Advanced uses of "This" in Javascript

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.