Variable scope is a fundamental concept that must be mastered in JavaScript. Knowing how var
, let
, and const
behave, and how hoisting and closure affect access to variable values will allow you to write more fluent and predictable code.
What Is Scope?
In JavaScript, scope refers to the current context of execution in which values and expressions are "visible" or accessible.
In JavaScript, there are two main types of scope:
- Global scope: Variables declared at the "top-level," outside any function or block.
- Local scope: Variables declared inside a function or block.
JavaScript also has the concept of lexical scoping, which means that a function's access to variables is determined by where it is written in the code, not where it's called from. Inner functions can access variables from their outer (enclosing) functions, creating a nested "scope chain."
var
, let
, and const
JavaScript provides three keywords for declaring variables: var
, let
, and const
. Each has distinct scoping behaviors.
var
: Function Scope and Hoisting
var
is function-scoped, which means it can be accessed anywhere within the function in which it is declared (even before its value is initialized). Or if it is used outside of a function, it is global. It is also hoisted, meaning the code acts as if the declaration were moved to the top of its scope at runtime (but not the assignment).
function f() {
console.log(x); // undefined (not ReferenceError)
var x = 10;
console.log(x); // 10
}
f();
console.log(x); // ReferenceError: x is not defined
In the above code, the declaration of x
is hoisted to the top of the function, so the first console.log
doesn’t throw an error, even though the value assignment hasn’t happened yet. Outside of the function, x
does not exist, so it will throw an error.
let
and const
: Block Scope and Temporal Dead Zone
let
and const
are block-scoped, meaning they are only accessible within the nearest set of {}
braces.
Unlike var
, which hoists the variable declaration to the top of its scope, const
and let
follow a model called The Temporal Dead Zone (TDZ). This refers to the scope between the start of a block and the point where the variable is declared. During this period, the variable exists but cannot be accessed; any attempt to do so will throw a ReferenceError
function demonstrateTdz() {
console.log(x); // undefined
console.log(y); // ReferenceError: Cannot access 'y' before initialization
var x = 1;
let y = 2;
console.log(x, y); // 1 2
}
demonstrateTdz();
Using let
or const
helps avoid confusion caused by hoisting, making code more predictable.
Block Scope Example
{
let a = 1;
{
const b = a + 1; // a is accessible here
var c = b + 1;
}
}
console.log(typeof a); // undefined
console.log(typeof b); // undefined
console.log(c); // 3
In this example, a
and b
are block-scoped, so they are not available outside their enclosing block. a
is available to the inner scope, however. c
is declared with var
, so it's scoped to the function it is declared in or, as in this case, the global scope.
Closures
A closure is a function scope that maintains access to the variables available in whatever scope it was defined in, regardless of where it is called from.
function makeCounter() {
let count = 0;
return function () {
count++; // from the outer scope
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
Closures are fundamental in JavaScript and commonly used in web development, particularly in event handlers and asynchronous code.
Summary
-
var
is function-scoped or globally scoped and hoisted. -
let
andconst
are block-scoped and do not behave as if hoisted (TDZ
). - Use
const
orlet
to limit the scope of your variables and avoid hoisting. - Scope and closures are key to many JavaScript patterns, including modules and callbacks.
Top comments (0)