Understanding THIS context in JS
How does variable definition work in JS? Why do functions inherit this?

I’m confused by ‘this’. Ok? I’ve said it.
- How does it get set?
- why is it different for arrow function?
- and why do functions not get one? these are the questions i want to answer in this article.
THIS does not refer to CONTEXT (lexical scope)
You should know that when you write a function like I do here:
var name = "alex"
function greet() { console.log("hi "+this.name);}
greet() // whats going on here?What does it print?Plain <script> in a browser, not a module. Same for the rest.
Show the output
hi alexCalled bare in a plain script, so this is window. A global var sticks to window, so this.name is "alex". In a module this throws instead: this is undefined there.
The same code will not run in Java, C or PHP. In fact it wouldn’t print alex in JS either if it weren’t for me defining the name using var. This is a bit of a side note but variables defined with var live in global scope rather than in the lexical context.
| defined | var | let & const |
|---|---|---|
| globally | global scope | global lexical context |
| function | function scope | function scoped lexical context |
| block | enclosing function (or global) scope | block scoped lexical context |
Here’s a quick demonstration of the difference:
if (true) { var v_name = "alex" let l_name = "alex"}
console.log(v_name) // "alex"console.log(l_name) // ReferenceError: l_name is not definedBut why does this.name returns globally scoped var? Thats because in non strict environment like browser, the globally scoped ‘this’ refers to the window object and var being defined in the global scope is set there. But really its just because all of the browser code technically runs inside the window object, if you think about it this way it becomes rather obvious that the vars should stick to it and functions would inherit it.
here are some sources for further reading https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz
THIS is syntactic sugar
Have you ever wondered how objects got their place in js? how does js treat objects? Take a look at this: Lets define a simple User object
class User { name = ""; constructor(name) { this.name = name; } greet() { console.log("hi "+this.name) }}When we call a function from said object instance what ends up happening is:
const alex = new User("alex")alex.greet()
// is equivalent to:alex.greet.call(alex)// oralex.greet.apply(alex, [])from this simple example we can already understand that this is whatever object the function gets called on - the thing left of the dot. No object, no this. It doesn’t care where the function was written, only how it was called.
so the following code won’t greet alex
const name = "alex";
function greet() { console.log("hi "+this.name);}
greet()
// equivalent togreet.call(undefined);What does it print?
Show the output
hi ␣this is still window, but const doesn’t stick to it. So this.name reads window.name, a real browser property that is an empty string by default. Not undefined. Browsers.
nor will this work
function setup(){ const name = "alex";
function greet() { console.log("hi "+this.name); }}
greet()
// would be equivalent to (if greet were in scope here)greet.call(undefined);What does it print?
Show the output
Uncaught ReferenceError: greet is not definedTrick question. greet only exists inside setup(), so out here there is nothing to call. It never gets far enough to be confused about this.
To better understand what I just wrote, go over the following example trying to predict the program’s output
Here’s our setup, a simple user class with this.name property and two greet function variants, one using a normal function the second using arrow function
class User { name = ""; constructor(name) { this.name = name; } normalGreet(){ console.log("hi "+this.name) } arrowGreet = () => { console.log("hi "+this.name) }}
let user = new User("alex");let miri = new User("miri")First question
let alex = new User("alex")
// firstalex.normalGreet();alex.arrowGreet();Hope i won’t spoil the fact that both work just fine printing our greeting message for alex - hi alex
second question
let alex = new User("alex");
let normal = alex.normalGreet;let arrow = alex.arrowGreet;
normal();arrow();What does it print?
Show the output
Uncaught TypeError: Cannot read properties of undefined (reading 'name')Code inside a class is always strict mode, so a detached normal() gets this === undefined instead of window, and reading .name off it throws. arrow() never gets its turn.
Here is where the problem start, can you spot the issue?
what happens when we assign the function to a variable before calling it?
As it turns out the normal function blows up with TypeError: Cannot read properties of undefined (reading 'name'), now think about why. We said that the normal function has a dynamic this property, and that it is calculated in run time. Think about it, we are calling greet function on nothing, it has no context of this, therefore how can it refer to this.name? (Class bodies always run in strict mode, so this is plain undefined here instead of quietly falling back to window.) It throws before arrow() even gets its turn.
In case of the arrow function, whose this is defined in definition, the context stays intact even after the assignment to a different variable. And it is exactly the difference between normal and arrow functions.
You might be asking yourself, do I really need to know that? in what context will I see this? The answer depends on your use of OOP in JS or any other framework. For example
<button onclick={alex.normalGreet}>Normal Greet</button><button onclick={alex.arrowGreet}>Arrow Greet</button>The code above can be viewed as assigning a function to an object property!
// this exactly copies our second examplebuttonElement.onclick = alex.normalGreetso for our tsx to work you would need to either use an arrow function in the User object, or pass it along like this
<button onclick={()=>alex.normalGreet()}>Normal Greet</button>See also: Livewire for WP, where an inline onclick hands this (the button) to the handler.
Third section is just for fun in case you want to better understand how bind, call and apply work:
let miri = new User("miri")
let normal = alex.normalGreet;let arrow = alex.arrowGreet;
normal = normal.bind(miri);arrow = arrow.bind(miri);
normal();arrow();What does it print?
Show the output
hi mirihi alexbind sets this for a plain function, so normal now greets miri. The arrow already took its this from where it was written, the alex instance, and bind can’t take it back.
The normal function prints hi miri. bind returns a new function with this nailed to whatever you pass it, and a normal function happily takes it.
The arrow function still prints hi alex. Arrow functions don’t have their own this at all - they grab it from wherever they were defined, which for a class field is the instance being constructed. bind, call and apply can try to hand it a different this, and the arrow just ignores them. It was born belonging to alex, and it will die belonging to alex.
So, back to my questions from the top. this gets set at call time, by whatever is left of the dot (or by call, apply and bind). Arrow functions are different because they never get a this of their own - they borrow the one from where they were written. And when you call a function on nothing, there’s nothing to set, so you get undefined (or window, if you’re in sloppy mode and unlucky).
Still a bit confused? Me too. Just less than when I started.
5 snippets, 5 guesses. The score lands here.
Comments
Comments live in GitHub Discussions (via giscus), so you need a GitHub account to join in.