Skip to content

Instantly share code, notes, and snippets.

@ajitid
Last active April 28, 2023 10:31
Show Gist options
  • Save ajitid/fc7d5b6129cbf30ccd6aff954e85e462 to your computer and use it in GitHub Desktop.
Save ajitid/fc7d5b6129cbf30ccd6aff954e85e462 to your computer and use it in GitHub Desktop.
Understanding `this`
"use strict";
// using strict mode as `this` behavior can change in a non-strict JS https://egghead.io/lessons/javascript-this-in-function-calls
// ESM files use `"use strict";` by default
// -------------
// Let's see what top level `this` is in different environments
/*
in browsers
this === globalThis === window
*/
/*
in ESM (files with .mjs extension or type=module)
this === undefined
globalThis === global // containing setTimeout, clearInterval...
*/
// console.log(this);
// console.log(globalThis === global);
/*
in CJS
this === module.exports
globalThis === global // containing setTimeout, clearInterval...
*/
// console.log(this === module.exports); // true
// module.exports = { xx: "yy" }; // reassignment happen
// console.log(this === module.exports); // false, as `this` still holds original module.exports obj.
/*
Because assigning to `this` is not allowed like:
this = {"something": "new"}
So in ESM we cannot change `this` from undefined.
But in CJS `this` refers to an object so we do something like:
*/
module.exports["global"] = "value";
/* so lets use CJS for this file. */
// ---------------
// add a `/` at the start of the next line to uncomment the block
/*
{
const a = {
v: 4,
fn() {
return this;
},
afn: () => {
return this;
},
};
console.log(a.fn());
console.log(a.afn()); // an arrow fn gives outer scope's `this`
console.log("--");
// rebinding helps to restore `this` ctx
let fn = a.fn;
console.log(fn());
fn = fn.bind(a);
console.log(fn());
console.log("--");
// bind doesn't work with arrow fns
fn = a.afn;
console.log(fn());
fn = fn.bind(a);
console.log(fn());
}
//*/
/*
{
// arrow fn behavior in classes https://www.youtube.com/watch?v=7bsA6Poxvy4
// the code below is unrelated so do watch the video first:
// With this code and the code written above, all I'm trying to convey is:
// - An arrow function uses closure to capture `this`, while to a normal func we bind a `this`.
// - In an object, an arrow fn will capture `this` not from the object, but from its outer scope,
// on the other hand an arrow func defined in class or function() will capture class or function()'s this
const l = console.log;
const o = {
val: 3,
fn() {
l(this);
},
afn: () => {
l(this);
},
};
o.fn();
o.afn();
console.log("--");
class C {
fn() {
l(this);
}
afn = () => {
l(this);
};
}
const c = new C();
c.fn();
c.afn();
console.log("--");
// `class` is a syntactic sugar on a constructor function that can create instances.
// So this will behave the same as class,
// (Only exception being here is that `fn()` is assigned on the instance, not on the prototype,
// which we could've emulated by doing `this.__proto__.fn = function() {...}` if we wanted to)
function FC() {
this.fn = function () {
l(this);
};
this.afn = () => {
l(this);
};
}
const fc = new FC();
fc.fn();
fc.afn();
}
//*/
/*
{
// The last thing I want to mention is function called with no `this` context will give `undefined`.
// This behavior is similar to the case when you do `const separatefn = classInstance.fn; separatefn();` and `this` inside `separatefn` becomes `undefined`.
function fn() {
console.log(this);
}
fn();
// again looping back to the video mentioned at the very top of this file: ^ above fn call will give undefined only if strict mode is enabled
}
//*/
@ajitid
Copy link
Author

ajitid commented Apr 27, 2023

Few more notes:

  • When we bind a normal function with .bind(), we can treat it like an arrow function (which captures this from its outer scope using closure): both of them will not lose this context when passed around
  • The easiest way to deduce what this value an arrow fn will contain in its definition would be to think about in this manner:
    • The arrow fn will takes whatever this its parent normal function has
    • If a normal function is not enclosing it, it will take the root this
    • (Seeking for a parent normal function for this otherwise falling back to root this is what I was calling as outer scope)
    • Methods in constructor functions (see line 120) take their this from the constructor function. And because a class is just a syntactic sugar on a constructor function, methods on it (see line 102) will behave the same way. A method on an object (see line 88) works in a similar manner too.
      • You can think as all three of them getting defined as this.fn = function() {}. And this is how they get a hold on this context.
    • Because of the points above, while we can assign an arrow function and a normal function to a plain object, only the normal function keeps object's this. The arrow function will rather try to find this in outer scope.
  • Because an arrow function captures this using closure (like it does to any other outer variable), we cannot change its this, simply because it doesn't depend on it.

@ajitid
Copy link
Author

ajitid commented Apr 27, 2023

Once you understand all that, check out this specific question https://javascript.info/object-methods#using-this-in-object-literal and click on the solution & go to its bottom to see the right answer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment