Last active
April 28, 2023 10:31
-
-
Save ajitid/fc7d5b6129cbf30ccd6aff954e85e462 to your computer and use it in GitHub Desktop.
Understanding `this`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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 | |
} | |
//*/ | |
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
Few more notes:
.bind()
, we can treat it like an arrow function (which capturesthis
from its outer scope using closure): both of them will not losethis
context when passed aroundthis
value an arrow fn will contain in its definition would be to think about in this manner:this
its parent normal function hasthis
this
otherwise falling back to rootthis
is what I was calling as outer scope)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.this.fn = function() {}
. And this is how they get a hold onthis
context.this
. The arrow function will rather try to findthis
in outer scope.this
using closure (like it does to any other outer variable), we cannot change itsthis
, simply because it doesn't depend on it.