Understanding the Scope Chain
The connections between scopes that are nested within other scopes is called the scope chain, which determines the path along which variables can be accessed. The chain is directed, meaning the lookup moves upward/outward only.”Lookup” Is (Mostly) Conceptual
In the previous chapter, we described the runtime access of a variable as a “lookup,” where the Engine has to start by asking the current scope’s Scope Manager if it knows about an identifier/variable, and proceeding upward/outward back through the chain of nested scopes (toward the global scope) until found, if ever. The lookup stops as soon as the first matching named declaration in a scope bucket is found. This suggestion of a runtime lookup process works well for conceptual understanding, but it’s not actually how things usually work in practice.The color of a marble’s bucket (aka, meta information of what scope a variable originates from) is usually determined during the initial compilation processing. Because lexical scope is pretty much finalized at that point, a marble’s color will not change based on anything that can happen later during runtime.
Shadowing
“Shadowing” might sound mysterious and a little bit sketchy. But don’t worry, it’s completely legit! Where having different lexical scope buckets starts to matter more is when you have two or more variables, each in different scopes, with the same lexical names. A single scope cannot have two or more variables with the same name; such multiple references would be assumed as just one variable. Consider:studentName variable on line 1 creates a RED(1) marble. The same named variable is declared as a BLUE(2) marble on line 3, the parameter in the printStudent(..) function definition.
What color marble will studentName be in the assignment statement and the console.log(studentName) statement? All three studentName references will be BLUE(2).
This is a key aspect of lexical scope behavior, called shadowing. The BLUE(2) studentName variable (parameter) shadows the RED(1) studentName. The parameter is shadowing the (shadowed) global variable.
Global Unshadowing Trick
It is possible to access a global variable from a scope where that variable has been shadowed, but not through a typical lexical identifier reference. In the global scope,var declarations and function declarations also expose themselves as properties on the global object. If you’ve written JS for a browser environment, you probably recognize the global object as window.
Consider this program:
window.studentName expression accesses the global variable studentName as a property on window. That’s the only way to access a shadowed variable from inside a scope where the shadowing variable is present.
This “trick” only works for accessing a global scope variable, and only one that was declared with var or function. Other forms of global scope declarations do not create mirrored global object properties:
Copying Is Not Accessing
Consider:special: special is copying the value of the special parameter variable into another container (a property of the same name). Of course, if you put a value in another container, shadowing no longer applies. But that doesn’t mean we’re accessing the parameter special; it means we’re accessing the copy of the value it had at that moment.
Illegal Shadowing
Not all combinations of declaration shadowing are allowed.let can shadow var, but var cannot shadow let:
SyntaxError is because the var is basically trying to “cross the boundary” of (or hop over) the let declaration of the same name, which is not allowed.
Summary:
let (in an inner scope) can always shadow an outer scope’s var. var (in an inner scope) can only shadow an outer scope’s let if there is a function boundary in between.Function Name Scope
Afunction declaration looks like this:
function declaration will create an identifier in the enclosing scope named askQuestion.
What about this program?
function declarations and function expressions is what happens to the name identifier of the function. Consider a named function expression:
ofTheTeacher is declared as an identifier inside the function itself:
ofTheTeacher declared inside the function rather than outside, but it’s also defined as read-only:
Arrow Functions
ES6 added an additionalfunction expression form to the language, called “arrow functions”:
=> arrow function doesn’t require the word function to define it. Arrow functions are lexically anonymous, meaning they have no directly related identifier that references the function.
Other than being anonymous (and having no declarative form),
=> arrow functions have the same lexical scope rules as function functions do. An arrow function, with or without { .. } around its body, still creates a separate, inner nested bucket of scope.
