Understanding Scope
The way we will approach learning about scope is to think of the process in terms of a conversation. But, who is having the conversation?
The Cast
Let’s meet the cast of characters that interact to process the program var a = 2;
, so we understand their conversations that we’ll listen in on shortly:
Engine: responsible for start-to-finish compilation and execution of our JavaScript program.
Compiler: one of Engine‘s friends; handles all the dirty work of parsing and code-generation (see previous section).
Scope: another friend of Engine; collects and maintains a look-up list of all the declared identifiers (variables), and enforces a strict set of rules as to how these are accessible to currently executing code.
For you to fully understand how JavaScript works, you need to begin to think like Engine (and friends) think, ask the questions they ask, and answer those questions the same.
Back & Forth
When you see the program var a = 2;
, you most likely think of that as one statement. But that’s not how our new friend Engine sees it. In fact, Engine sees two distinct statements, one which Compiler will handle during compilation, and one which Engine will handle during execution.
So, let’s break down how Engine and friends will approach the program var a = 2;
.
The first thing Compiler will do with this program is perform lexing to break it down into tokens, which it will then parse into a tree. But when Compiler gets to code-generation, it will treat this program somewhat differently than perhaps assumed.
A reasonable assumption would be that Compiler will produce code that could be summed up by this pseudo-code: “Allocate memory for a variable, label it a
, then stick the value 2
into that variable.” Unfortunately, that’s not quite accurate.
Compiler will instead proceed as:
Encountering
var a
, Compiler asks Scope to see if a variablea
already exists for that particular scope collection. If so, Compiler ignores this declaration and moves on. Otherwise, Compiler asks Scope to declare a new variable calleda
for that scope collection.Compiler then produces code for Engine to later execute, to handle the
a = 2
assignment. The code Engine runs will first ask Scope if there is a variable calleda
accessible in the current scope collection. If so, Engine uses that variable. If not, Engine looks elsewhere (see nested Scope section below).
If Engine eventually finds a variable, it assigns the value 2
to it. If not, Engine will raise its hand and yell out an error!
To summarize: two distinct actions are taken for a variable assignment: First, Compiler declares a variable (if not previously declared in the current scope), and second, when executing, Engine looks up the variable in Scope and assigns to it, if found.
Compiler Speak
We need a little bit more compiler terminology to proceed further with understanding.
When Engine executes the code that Compiler produced for step (2), it has to look-up the variable a
to see if it has been declared, and this look-up is consulting Scope. But the type of look-up Engine performs affects the outcome of the look-up.
In our case, it is said that Engine would be performing an “LHS” look-up for the variable a
. The other type of look-up is called “RHS”.
I bet you can guess what the “L” and “R” mean. These terms stand for “Left-hand Side” and “Right-hand Side”.
Side… of what? Of an assignment operation.
In other words, an LHS look-up is done when a variable appears on the left-hand side of an assignment operation, and an RHS look-up is done when a variable appears on the right-hand side of an assignment operation.
Actually, let’s be a little more precise. An RHS look-up is indistinguishable, for our purposes, from simply a look-up of the value of some variable, whereas the LHS look-up is trying to find the variable container itself, so that it can assign. In this way, RHS doesn’t really mean “right-hand side of an assignment” per se, it just, more accurately, means “not left-hand side”.
Being slightly glib for a moment, you could also think “RHS” instead means “retrieve his/her source (value)”, implying that RHS means “go get the value of…”.
Let’s dig into that deeper.
When I say:
console.log( a );
The reference to a
is an RHS reference, because nothing is being assigned to a
here. Instead, we’re looking-up to retrieve the value of a
, so that the value can be passed to console.log(..)
.
By contrast:
a = 2;
The reference to a
here is an LHS reference, because we don’t actually care what the current value is, we simply want to find the variable as a target for the = 2
assignment operation.
Note: LHS and RHS meaning “left/right-hand side of an assignment” doesn’t necessarily literally mean “left/right side of the =
assignment operator”. There are several other ways that assignments happen, and so it’s better to conceptually think about it as: “who’s the target of the assignment (LHS)” and “who’s the source of the assignment (RHS)”.
Consider this program, which has both LHS and RHS references:
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
The last line that invokes foo(..)
as a function call requires an RHS reference to foo
, meaning, “go look-up the value of foo
, and give it to me.” Moreover, (..)
means the value of foo
should be executed, so it’d better actually be a function!
There’s a subtle but important assignment here. Did you spot it?
You may have missed the implied a = 2
in this code snippet. It happens when the value 2
is passed as an argument to the foo(..)
function, in which case the 2
value is assigned to the parameter a
. To (implicitly) assign to parameter a
, an LHS look-up is performed.
There’s also an RHS reference for the value of a
, and that resulting value is passed to console.log(..)
. console.log(..)
needs a reference to execute. It’s an RHS look-up for the console
object, then a property-resolution occurs to see if it has a method called log
.
Finally, we can conceptualize that there’s an LHS/RHS exchange of passing the value 2
(by way of variable a
‘s RHS look-up) into log(..)
. Inside of the native implementation of log(..)
, we can assume it has parameters, the first of which (perhaps called arg1
) has an LHS reference look-up, before assigning 2
to it.
Note: You might be tempted to conceptualize the function declaration function foo(a) {...
as a normal variable declaration and assignment, such as var foo
and foo = function(a){...
. In so doing, it would be tempting to think of this function declaration as involving an LHS look-up.
However, the subtle but important difference is that Compiler handles both the declaration and the value definition during code-generation, such that when Engine is executing code, there’s no processing necessary to “assign” a function value to foo
. Thus, it’s not really appropriate to think of a function declaration as an LHS look-up assignment in the way we’re discussing them here.
Engine/Scope Conversation
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
Let’s imagine the above exchange (which processes this code snippet) as a conversation. The conversation would go a little something like this:
Engine: Hey Scope, I have an RHS reference for
foo
. Ever heard of it?Scope: Why yes, I have. Compiler declared it just a second ago. He’s a function. Here you go.
Engine: Great, thanks! OK, I’m executing
foo
.Engine: Hey, Scope, I’ve got an LHS reference for
a
, ever heard of it?Scope: Why yes, I have. Compiler declared it as a formal parameter to
foo
just recently. Here you go.Engine: Helpful as always, Scope. Thanks again. Now, time to assign
2
toa
.Engine: Hey, Scope, sorry to bother you again. I need an RHS look-up for
console
. Ever heard of it?Scope: No problem, Engine, this is what I do all day. Yes, I’ve got
console
. He’s built-in. Here ya go.Engine: Perfect. Looking up
log(..)
. OK, great, it’s a function.Engine: Yo, Scope. Can you help me out with an RHS reference to
a
. I think I remember it, but just want to double-check.Scope: You’re right, Engine. Same guy, hasn’t changed. Here ya go.
Engine: Cool. Passing the value of
a
, which is2
, intolog(..)
.…
Quiz
Check your understanding so far. Make sure to play the part of Engine and have a “conversation” with the Scope:
function foo(a) {
var b = a;
return a + b;
}
var c = foo( 2 );
Identify all the LHS look-ups (there are 3!).
Identify all the RHS look-ups (there are 4!).
Note: See the chapter review for the quiz answers!