I think I wanted to explain the difference between arrow and normal functions, but I kinda explained this and closures by accident

Glossary
A
bindingis a variable name (this I borrow from the previous glossary). Can be classified into one of two categories:A
lexical binding, which can be determined at the lexing step of the compilation. Any variable or function declaration in user-authored code.A
dynamic binding, which can only be determined at runtime. Thethisbinding, i.e.
A
normal functionis every kind of function but an arrow one. Okay,async functionsandgeneratorssometimes neither.
What’s this article about?
Well... it’s not about execution contexts. It’s more about what execution contexts are not. It assumes you already know... wait, am I not just writing the same as in the previous article? I guess I just don’t know what this article is about. But it aims to go a bit deeper into the this binding in arrow functions and explores executions context as an inevitable side-effect. Also, worth mentioning it’s going to go as deep as my current knowledge allows me to... shit, I did it again.
Once I’ve finished writing it, I think I’ve kinda explained something about the difference between arrow and normal functions, this and closures.
The misconception
❓ Arrow functions don't define their own
this. In arrow functions,thisrefers to the same value asthisin their outer lexical scope.
I don't know who first described it that way, but everyone repeats it and it has always bugged me. Not because it's necessarily wrong but because, to me, it's a bad explanation.
First of all, I feel like everyone repeats it but doesn’t understand its meaning.
Second, it kinda makes it feel as if the value of this in an arrow function could be (or even was!) determined at compile-time.
Yes, in arrow functions, this refers to the same value as this in their outer lexical scope. But I have two issues:
To determine
thisin their outer lexical scope, at some point you have to ask yourself what’s the value ofthisin their outer lexical scope! And there’s a good chance that can’t be determined at compile-time if there’s anynormal functionup in theirscope chain. So you can’t just look at a representation of thescope chainof your code (otherwise Bubbl.es would be amazing!) and reason about thethiskeyword. You most likely have to think about run-time. Simply because thethiskeyword is completely pointless in an “only-arrow functions” environment.What’s its “outer lexical scope”, anyway?
I’d like to rephrase it as:
✅ Arrow functions don't bind a value to
this. In arrow functions, the value ofthisis resolved through a lexical lookup, as if it was alexical binding. But beware, the whole lookup might not be entirely lexical.
An example of why I think it matters:
function Vector(x, y) {
this.x = x
this.y = y
}
Vector.prototype.add = function(vector) {
const sum = () => {
console.log(this)
this.x = this.x + vector.x
this.y = this.y + vector.y
}
sum()
}
const v1 = new Vector(0, 0)
const v2 = new Vector(1, 1)
v1.add(v2) // logs { x: 0, y: 0 }
v2.add(v1) // logs { x: 1, y: 1 }
A lexical lookup suggests just by looking at the code arrangement of the function is enough to understand the whole story. But that’s not true.
We can do better
const vector = {
x: 0,
y: 0,
add: (vector) => {
this.x = this.x + vector.x
this.y = this.y + vector.y
}
}
vector.add(/** assuming it's been declared somewhere */ otherVector)
Assuming the snippet above runs in strict mode and in the global execution context, running vector.add(otherVector) will produce a TypeError since the value of this will be undefined. That's because the arrow function is declared when the object is initialized, in the global context, where this' value is undefined (again, assuming strict mode).
The snippet adds up to the misconception quote:
Arrow functions don't define their own
this, because if it did, it would point tovectorfor that particular invocation we have discussed.In arrow functions,
thisrefers to the same value asthisin their outer lexical scope, and since its "outer lexical scope" is the "globalscope", that's why it'sundefinedin the snippet above (again, instrict mode).
Is the scope the same thing as an execution context, though?
Execution contexts and scopes
💪🏼 Ok, but what’s an execution context, anyway?
💡 When a function is invoked, an execution context is created to hold the state necessary to run it.
It’s the set of stuff a function needs to be run, like how to resume execution of the code that invoked it once finished and other information about its surroundings.
🥞 It’s also the thing that gets pushed into the call stack (I’ll write about the call stack and, when I do, I’ll make a cross-reference from this blog to that and from that to this and everything will make sense, you’ll see).
⛓️ The scope (or what we usually mean when we speak about the scope) is a spec term, but it’s broader meant kinda as a conceptual one.
💡 I like to define the
scopeof a variable as the region of the code where that variable is available. Then I define thescope(or thescope chain) as the set of regions that limit the availability of variables.
🧩 But, at least in JS, that concept partially maps to a concrete concept that represents one of the layers of state that compose an execution context: a function environment record.
💡 A
function environment recordis some memory space that holds thelexical bindingsavailable in a particularexecution context. However, it also holds somedynamic bindings: mainly, the one that catches our attention is thethiskeyword.
Function environment records is the way JS controls the availability of variables in a certain execution context. But also, function environment records hold a reference to an outer function environment record (the one that is part of the execution context that declared the function this function environment record’s execution context belongs to). And that resembles the scope chain a lot! If not for the dynamic bindings, it’d perfectly map to our concept of scope.
So an execution context kinda has a 1:1 relationship with a function environment record which, at the same time, kinda maps to our concept of scope. But just kinda.
Can we explain what’s this?
The
thiskeyword is adynamic bindingthat belongs to thefunction environment recordof a function’sexecution contextand whose main goal is dynamically accessing different objects in order to reuse code.
Learn more about this: The rules of this
Wasn’t the misconception related to arrow functions?
Yes, yes! I’m trying to get there! But first, did you know functions are objects? Learn more about it:
So to speak about arrow functions, let’s first deviate again and speak about normal functions.
Let’s imagine the declarations and invocation of a normal function called fn. Here, take a look at some pseudocode:
outerFn() {
...
fn() {
...
}
...
fn()
}
outerFn()
So we run outerFn, and then...
Declaration of fn
When declared, it’s just the function object.
Invocation of fn
When invoked, before running its code, we need to know how to.
Thus we create a brand new execution context for it. Different from the ones previously created for fn, if it had been previously run. Different from the ones that will be created for fn if run again. A new one per each invocation.
As discussed, while creating fn's execution context, JS will attach a function environment record to it which will hold uninitialized (thus undefined) lexical bindings for all the variables and functions declared inside of the function (hi, hoisting!). These won’t be out of anywhere, though; remember how they are lexical and that has something to do with compiling? Yep, JS knows well what those binding names are.
Also from the get-go, every function declaration declared in it will be initialized, so a function object will be created for every function declaration that fn contains (hi again, hoisting —now with initialization of function declarations because the spec says so—). I briefly mentioned it at the end of:
Hoisting without mentioning code rearrangement
As discussed too, that function environment record won't only hold those bindings, but also has a reference to an outer function environment record: the function environment record that belongs to the execution context where fn was declared. We can even give it a name: in the above code snippet, when we ran outerFn, an execution context just like the one we’re describing for fn was created for it. And outerFn's execution context, just like fn's, has its own function environment record, which will be fn’s function environment record outer function environment record. Don’t forget about outerFn's function environment record, because we will come back to it soon!
⚠️ Oh, a minor detail! If
outerFn
returned
fn, orscheduled
fn's execution for a later time whenouterFnwas already finished, thus out of thecall stack,fn, when run, would still have access to the variables declared byouterFnbecausefn'sfunction environment recordwould still have access toouterFn'senvironment record.Yes, that’s
closureswithout magic. Oh, did I say minor? Sorry, it’s actually a huge detail.
As discussed too (the last one, I promise), that function environment record won't only hold lexical bindings, but also some dynamic bindings (hi, this among others!). So the value of this will potentially be different every time fn is executed depending on how it's executed. It may have been called using Function#call or Function#apply. It may be bound through Function#bind. It may be a property anywhere in the prototype chain of an object and called through it.
What if fn was an arrow function?
Same stuff, just a couple of differences here and there:
When thinking about arrow functions as
function objects, their[[Construct]]slot is empty.Arrow functions never bind a value to their
function environment record'sdynamic bindings: northis, norsuper, norarguments(who’s that sucker anyway?). Instead, the resolution algorithm for these is a lexical lookup, as in anylexical bindings, through thescope chain(orfunction environment recordschain), all the way up to theglobal execution context, only stopping if finding a binding they can resolve to in their way. I told you to rememberouterFn'sfunction environment record, didn’t I? That’s where the lookup will start. The mechanism that explainsclosuresand howthisis resolved in arrow functions is inherently... the same!
The [[Construct]] bit also holds true for async functions and generators, while thethis binding doesn't (unless... you know... they are also arrows).


