Javascript under the hood - Scopes

This article explores the scoping mechanism in JS. Mastering scope is imperative to fully understand more advanced concepts such as closures and callback functions.

Simply put, scoping refers to a process employed by the JS compiler during the compilation phase for where to look for things.

The technical term for "things" is identifiers, and these identifiers can be anything from primitives(strings, integers, boolean, etc..) to functions. Javascript uses lexical scoping to look for identifiers, so JS is lexically scoped. Another way to say this is that lexical scope is compile time scope. I.e., at the time that your code gets compiled, the compiler will note where the identifier is PHYSICALLY present, and the identifier's value can only be accessed in that scope.

Well, I mentioned the word compiler a couple of times in the paragraph above, so let's talk for a couple of minutes about how javascript code is parsed. For the sake of clarity, let's assume that the javascript file is added to a web page. Every browser has a Javascript engine that will compile the code and then execute it. This means that your javascript code gets two pass-throughs; The first pass-through is done by the JS engine to compile the code (the compilation phase) After a few micro-seconds, the the second pass-through is done by the JS Engine to execute the code (the execution phrase).

Let's say we have the following line:

var name = "Jack";

As a human, you'll read the above line is** one sentence**, something like "var name is equal to jack". Well, that's not how the line is read by the JS engine, which splits that sentence into two separate sentences. The first part of the sentence (var name) is processed during compile time. In technical lingo, the compiler will add a variable declaration for an identifier called "name" in the global scope (this will be explained in greater detail below). The second part of the sentence (= "Jack") is processed during run-time. During runtime, the JS engine will assign the value "Jack" to a variable called name.

So let's say we have the following program:

var name = "John"; //line 1 function setName(name) { //line 2 name = "Joe"; //line 3 function nestedFunc() { // line 4 var name = "Lauren"; //line 5 anotherName = "jack"; //line 4 } nestedFunc(); // line 7 console.log(name); 8 } setName(); //line 9 nestedFunc(); // line 10 console.log(name); //line 11

During the compilation phase, the compiler will go through the code line by line until all identifiers have been added to their respective scopes.

To elucidate this concept easily, let's pretend the compiler (C) is having the following conversation with the scope manger (SM), starting from the first line:

A friendly conversation between the compiler and the scope manager

C: Hey global scope, I have a variable declaration for an identifier called name. Put that in your list of scopes.
SM: Done! I have added this in global scope.

C: Hey global scope, I have a function declaration for an identifier called setName. Put that in your list of scopes.
SM: Done! I have added setName to my list.
C: (internal monologue) I realize that this a function, so I'll recursively descend into this function until all identifiers have been properly scoped.
C: Hey SM, I have a variable declaration for an identifier called name, put that in your list of scopes. ((Did you catch this ? setName has a parameter called name, which is what the compiler is referring to! This is known as an implicitly declared variable)).
SM: Done! I have added name to my list.

C: Hey scope of setName, I have a function declaration for an identifier called nestedFunc. Put that in your list of scopes.
SM: Done! I have added nestedFunc to my list.
C: (internal monologue) I realize that this a function, so I'll recursively descend into this function until all identifiers have been properly scoped.
C: Hey scope of nestedFunc, I have a variable declaration for an identifier called name, put that in your list of scopes. SM: Done! I have added name to my list.

C: There are no other variables or function declarations in this file, so my job here is done! Hey, JS engine, here's the compiled version of the program.

There are a couple of interesting take-aways from the above conversation:

  1. During the compilation process, lines 7 through 11 are ignored because there are no function or variable declarations happening.
  2. There were no variable assignments happening (e.g. for line 1, the compiler never assigned the value "john" to the variable name).

Both of these take-aways will be addressed in the execution phase! Speaking of, for the purpose of simplicity, here's what the compiled version of the program will look like:

name = "John"; //line 1 setName("Ali"); //line 2 nestedFunc(); //line 3 console.log(name); //line 4

Notice that since all function and variable declarations have been processed during the compilation phase, they are no longer present; all that's left are the variable assignment (line 1) and the function executions (line 2 through 4).

Yet another, even friendlier conversation between the javascript engine (JSE) and the scope manager (SM).

JSE: Hey global scope, I have an identifier called name, can I get a reference for this identifier ?
SM: Yes! I have this identifier registered in my global scope, here's the reference. returns the reference for this variable, which is the pointer for where this variable is stored in memory.
JSE: Great! I'll assign the value "John" to this variable.

JSE: Hey global scope, I have an identifier called setName, can I get a reference for this identifier ?
SM: Yes! I have this identifier registered in my global scope, here's the reference. returns the reference for this variable, which is the pointer for where this variable is stored in memory.
JSE: Ahh! I see that this is a function, and the parenthesis tell me I should execute this function, so I'll put this on the call stack to be executed.

As a reminder, here's the setName function:

function setName(name) { function nestedFunc() { var name = "Lauren"; anotherName = "jack"; } nestedFunc(); console.log(name); ??? }

JSE: Hey scope of setName, I have an identifier called name, can I get a reference for this identifier ?
SM: Yes! I have this identifier registered in my scope, here's the reference. returns the reference for this variable, which is the pointer for where this variable is stored in memory.
JSE: Great! I'll assign the value "Ali" to this variable.

Notice how the value "Ali" is assigned to the local variable name and NOT the global variable name, whose value is still "John".

JSE: Hey scope of setName, I have an identifier called nestedFunc, can I get a reference for this identifier ?
SM: Yes! I have this identifier registered in my scope, here's the reference. returns the reference for this variable, which is the pointer for where this variable is stored in memory.

JSE: Ahh! I see that this is a function, and the parenthesis tell me I should execute this function, so I'll put this on the call stack to be executed!

JSE: Hey, scope of nestedFunc, I have an identifier called name, can I get a reference for this identifier ?
SM: Yes! I have this identifier registered in my scope, here's the reference. returns the reference for this variable, which is the pointer for where this variable is stored in memory.
JSE: Great! I'll assign the value "Lauren" to this variable.

((Notice how the value "Lauren" is assigned to the identifier name registered in the scope of nestedFunc and NOT to the identifier name registered in the scope of setName, whose value is still "Ali". The ability of the program to have a variable in an inner scope that has the same name as a variable in the outer scope is known as shadowing. That is, the variable in the inner scope shadows the variable with the same name in the outer scope. In our example, name in the scope of nestedFunc shadows name in the scope of setName. Note that in turn, the identifier name in the scope of setName shadows the identifier name in the global scope!))

JSE: Hey scope of nestedFunc, I have an identifier called anotherName, can I get a reference for this identifier ?
SM:, Hmm, I don't have a reference for this identifier, sorry!
JSE: That's alright! I'll search for this identifier one scope upward. Hey scope of setName, I have an identifier called anotherName, can I get a reference for this identifier ?
SM:, Hmm, I don't have a reference for this identifier, sorry!
JSE: That's alright! I'll search for this identifier one scope upward. Hey global scope, I have an identifier called anotherName, can I get a reference for this identifier ?
SM:, Hmm, I don't have a reference for this identifier, but I'll create one for you! Here you go: * creates and then returns the reference for this variable, which is the pointer for where this variable is stored in memory.*

NoTE, change JSE to Javascript Runtime