Shadowing in JavaScript: What You Need to Know

What is Shadowing and How Does it Affect Your JavaScript Code?

Shadowing in JavaScript: What You Need to Know

What is shadowing

“Shadowing” might sound mysterious and a little bit sketchy.

But don’t worry, it’s completely legit!

Consider the following code:

var studentName = "Suzy"; //global scope 
function printStudent(studentName) {
//local scope of function printStudent
studentName = studentName.toUpperCase();
console.log(studentName);
}
printStudent("Frank");
// FRANK
printStudent(studentName);
// SUZY
console.log(studentName);
// Suzy

If you notice in the above code inside the printStudent we have a statement:

 studentName = studentName.toUpperCase();

There are two declarations of the variable studentName one in the global scope and the other in function. So what do you think this statement will refer to which declaration of this variable? To refer to this your JS engine will first check that is this variable student name already present in the current scope where this variable is referred, you can see that it is already available in this scope so will will take the value which is assigned to it here: ’Frank’.This is a key aspect of lexical scope behavior, called shadowing. The printStudent function’s studentName variable (parameter) shadows the global scope studentName. So, the parameter is shadowing the (shadowed) global variable.

Global Unshadowing Trick:

Please beware: leveraging the technique I’m about to describe is not very good practice, as it’s limited in utility, confusing for readers of your code, and likely to invite bugs to your program. I’m covering it only because you may run across this behaviour in existing programs, and understanding what’s happening is critical to not getting tripped up. 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 (of the same name as the identifier) on the global object—essentially an object representation of the global scope.

If you’ve written JS for a browser environment, you probably recognize the global object as window.

consider this code:

var studentName = "Suzy";
function printStudent(studentName) {
console.log(studentName);
console.log(window.studentName);
}
printStudent("Frank");
// "Frank"
// "Suzy

Notice the window.studentName reference? This expression is accessing the global variable studentName as a property on window.

This little “trick” only works for accessing a global scope

variable (not a shadowed variable from a nested scope), and even then, only one that was declared with var or function.

var one = 1;
let notOne = 2;
const notTwo = 3;
class notThree {}
console.log(window.one); // 1
console.log(window.notOne); // undefined
console.log(window.notTwo); // undefined
console.log(window.notThree); // undefined

Variables (no matter how they’re declared!) that exist in any other scope than the global scope are completely inaccessible from a scope where they’ve been shadowed:

Consider this code :

var special = 42;
function lookingFor(special) {
// The identifier `special` (parameter) in this
// scope is shadowed inside keepLooking(), and
// is thus inaccessible from that scope.
function keepLooking() {
var special = 3.141592;
console.log(special);
console.log(window.special);
}
keepLooking();
}
lookingFor(112358132134);
// 3.141592
// 42

The global special is shadowed by the

special (parameter), and the parameter special is itself

shadowed by the special inside keepLooking().

We can still access the global special using the indirect reference window.special But there’s no way for keepLooking() to access the parameter special that holds the number 112358132134.

Copying Is Not Accessing

What about we store this parameter as a property of the object and then try to access it? See below code :

var special = 42;
function lookingFor(special) {
var another = {
special: special
};
function keepLooking() {
var special = 3.141592;
console.log(special);
console.log(another.special); // Ooo, tricky!
console.log(window.special);
}
keepLooking();
}
lookingFor(112358132134);
// 3.141592
// 112358132134
// 42

Oh! So does this object technique disprove my claim that the special parameter is “completely inaccessible” from inside keepLooking()? No, the claim is still correct. 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 (unless another was shadowed, too!). 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, by way of another container (object property).

We cannot reassign the special parameter to a different value from inside keepLooking().

Illegal Shadowing

Not all combinations of declaration shadowing are allowed. let can shadow var, but var cannot shadow let:

function something() {
var special = "JavaScript";
{
let special = 42; // totally fine shadowing
// ..
}
}
{
// ..
{
let special = "JavaScript";
{
var special = "JavaScript";
// ^^^ Syntax Error
// ..
}
}
}

Notice in the another() function, the inner var special

declaration is attempting to declare a function-wide special, which in and of itself is fine (as shown by the something() function).

The real reason it’s raised as a SyntaxError is because the

var is trying to “cross the boundary” of (or hop over)the let declaration of the same name, which is not allowed. That boundary-crossing prohibition effectively stops at each function boundary.

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.