Skip to content

JavaScript Scope

Scope in JavaScript determines the accessibility (visibility) of variables, functions, and objects during runtime. Understanding scope is crucial for writing predictable and bug-free code.

JavaScript has primarily three types of scope:

  • Global Scope: Variables declared outside of any function or block have global scope. They are accessible from anywhere in your JavaScript code.
  • Function Scope (Local Scope for var): Variables declared with var inside a function are scoped to that entire function. They are not accessible outside the function.
  • Block Scope (Local Scope for let and const): Variables declared with let or const inside a block (e.g., code enclosed in curly braces {...} like in an if statement, for loop, or just a standalone block) are scoped to that block. They are not accessible outside that block.

A variable declared outside any function or block becomes a global variable.

const globalMessage = "Hello from global scope!"; // Global variable
function showMessage() {
console.log(globalMessage); // Accessible inside the function
}
showMessage();
console.log(globalMessage); // Accessible outside the function

In a browser environment, global variables typically become properties of the window object.

var globalVarInBrowser = "I am on window"; // Using var makes it a window property
let globalLetInBrowser = "I am not directly on window"; // Not directly a window property
const globalConstInBrowser = "Me neither"; // Not directly a window property
console.log(window.globalVarInBrowser); // "I am on window"
// console.log(window.globalLetInBrowser); // undefined

Caution with Globals: Overuse of global variables can lead to naming conflicts and make code harder to maintain and debug. It’s generally best practice to minimize their use.

Variables declared with var inside a function are accessible anywhere within that function, even before their declaration (due to hoisting), but not outside it.

function myFunctionScope() {
var carName = "Volvo"; // Function-scoped
console.log("Inside function: " + carName);
if (true) {
var color = "Red"; // Still function-scoped, not block-scoped
}
console.log("Color inside function: " + color); // Accessible
}
myFunctionScope();
// console.log(carName); // Error: carName is not defined outside the function
// console.log(color); // Error: color is not defined outside the function

Variables created with var are hoisted to the top of their function scope, meaning their declaration (but not initialization) is moved to the top. This can sometimes lead to confusing behavior if not understood.

ES6 introduced let and const, which provide block scope. This means variables declared with let or const are only accessible within the block ({...}) they are defined in.

function myBlockScope() {
let vehicle = "Car"; // Function-scoped (block is the function itself)
const year = 2023;
if (true) {
let model = "Tesla"; // Block-scoped to this if-block
const isElectric = true; // Block-scoped to this if-block
console.log("Inside if-block: " + model); // Accessible
console.log("Vehicle in if: " + vehicle); // Accessible (from outer scope)
}
// console.log(model); // Error: model is not defined here
// console.log(isElectric); // Error: isElectric is not defined here
console.log("Outside if-block: " + vehicle);
}
myBlockScope();
{
let isolatedVar = "I am in a standalone block";
console.log(isolatedVar);
}
// console.log(isolatedVar); // Error: isolatedVar is not defined here

let and const are also hoisted, but unlike var, they are not initialized. Accessing them before declaration results in a ReferenceError. This period between the start of the block and the declaration is often called the “Temporal Dead Zone” (TDZ).

Block scope helps prevent bugs related to variable redeclaration and unintended modifications, making code cleaner and more predictable.

JavaScript uses lexical scoping. This means that the scope of a variable is determined by its location within the source code at the time the code is written (lexed/parsed), not where the function is called.

An inner function has access to the scope of its outer functions. This is what makes closures possible.

const outerValue = "Outer";
function outer() {
const middleValue = "Middle";
function inner() {
const innerValue = "Inner";
console.log(outerValue); // Accesses global scope
console.log(middleValue); // Accesses outer function's scope
console.log(innerValue); // Accesses its own scope
}
inner();
}
outer();

In non-strict mode, assigning a value to an undeclared variable automatically creates a global variable. This is a common source of bugs.

function createGlobalImplicitly() {
// In non-strict mode, this creates a global variable 'implicitGlobal'
// In strict mode, this throws a ReferenceError
implicitGlobal = "I am an accidental global!";
}
// createGlobalImplicitly();
// console.log(implicitGlobal); // Accessible globally if not in strict mode

Strict Mode ('use strict';) helps prevent this. When strict mode is enabled (by adding 'use strict'; at the beginning of a script or function), assigning to an undeclared variable throws a ReferenceError.

function strictFunction() {
'use strict';
// undeclaredVar = "This will throw an error"; // ReferenceError
let declaredVar = "This is fine";
console.log(declaredVar);
}
// strictFunction();

It is highly recommended to always use strict mode and always declare variables with let, const, or (less preferably for new code) var.

  • Global variables: Live as long as your application (web page or Node.js process) lives.
  • Local variables (function or block-scoped): Are created when their containing function is called or block is entered, and are typically destroyed (garbage collected) when the function/block execution finishes, unless they are part of a closure that is still accessible.

Function arguments (parameters) also behave like local variables within the function’s scope.

When JavaScript looks for a variable, it starts in the current scope and, if not found, moves up the scope chain (to parent scopes) until it finds the variable or reaches the global scope. If not found in the global scope, a ReferenceError occurs.

If a variable in an inner scope has the same name as a variable in an outer scope, the inner variable “shadows” (hides) the outer one. The inner scope will use its own variable.

let x = "global x";
function testShadowing() {
let x = "local x in function"; // Shadows the global x
console.log(x); // "local x in function"
if (true) {
let x = "local x in block"; // Shadows the function-scoped x
console.log(x); // "local x in block"
}
console.log(x); // "local x in function"
}
testShadowing();
console.log(x); // "global x"
  • Minimize global variables. Use modules (ES6 modules or older patterns) to encapsulate code.
  • Prefer let and const over var for block scoping and to avoid common var pitfalls.
  • Declare variables in the narrowest scope possible.
  • Use 'use strict'; to catch common errors like accidental globals.