HOLIDAY SALE! Save 50% on Membership with code HOLIDAY50. Save 15% on Mentorship with code HOLIDAY15.

6) Advanced Arrays, Strings and Functions Lesson

The Basics of Scope in JavaScript

15 min to complete · By Ian Currie

In JavaScript, the concept of scope determines where variables and functions are accessible within your code. This is a fundamental aspect, especially when dealing with nested functions and loops. In this lesson you'll unravel the intricacies of block scope, and how JavaScript handles variable accessibility and naming in nested structures.

What is Scope?

Think of scope as the set of rules that manage the visibility or accessibility of variables. In JavaScript, two primary types of scope exist: global and local. Global scope refers to variables accessible anywhere in your code, while local scope pertains to variables accessible only within a specific function or block.

Nesting Functions and Loops

When you nest functions within each other or use loops inside functions, each nested level creates its own local scope. Here's an intriguing part: variables with the same name can coexist in different scopes without conflict, thanks to JavaScript's lexical scoping.

Heres an example:

let name = 'Tania';
console.log(name); // Tania

function logName() {
  let name = 'Ahmed';
  console.log(name);
}

logName(); // Ahmed

console.log(name); // Tania

In this example, the name variable is declared in the global scope. Within the logName() function, a new variable with the same name is declared. This variable is local to the function, and it shadows the global variable with the same name. This is why the function logs Ahmed instead of Tania.

var vs let vs const

Before we dive into block scope, let's briefly review the differences between var, let, and const.

var is the oldest keyword for declaring variables in JavaScript. It has function scope, meaning it variables declared with var within a function are limited to that function, but if declared outside, they are globally accessible.

var declarations are hoisted to the top of their scope, allowing them to be used before they are actually declared. Given its quirks, many developers prefer let and const for their clarity and block-level scoping.

let and const are newer keywords for declaring variables. They have block scope, meaning they are only accessible within the block they are declared in. let variables can be reassigned, while const variables cannot. Both let and const are not hoisted, and they are the preferred keywords for declaring variables in modern JavaScript.

In this course, you've been advised to stick to always using let. It just makes things simpler to get started. Later on, you can learn more about const and when you might want to use it.

Block Scope and let, const

Block scope, as applied in JavaScript, is closely linked to the use of let and const for declaring variables. These declarations are confined to the block (enclosed by curly brackets {}) in which they are defined. This behavior differs from variables declared with var, whi ch have function scope, not block scope.

Consider this for loop:

for (let i = 0; i < 1000; i++) {
  // Inner block
}
// Outer block

Variables declared within this inner block (like i in our loop) are not accessible outside of it.

Practical Example: Variable Scope in Loops

Let's explore this concept with an example of a very common mistake for beginners to make, declaring a variable inside a loop:

for (let i = 0; i < 1000; i++) {
  let sum = 0;
  sum += 1;
}

console.log(sum); // ReferenceError: sum is not defined

There are two issues with this code, both related to scope. First, sum is declared inside the for loop, making it local to that block. This means it is not accessible outside of the loop. This is why trying to log sum outside the loop results in an error. Second, sum is re-initialized to 0 with each iteration of the loop, so it will always equal 1 after the loop.

To accumulate the value of sum across iterations, declare it in the outer scope:

let sum = 0;

for (let i = 0; i < 1000; i++) {
  sum += 1;
}

console.log(sum); // Outputs: 1000

In this revised example, sum is declared in the global scope, allowing the for loop to access and modify it across each iteration.]

Lexical Scoping: A Hierarchical Approach

Lexical Scoping, also known as static scoping, refers to the decision made by JavaScript about where variables and functions are accessible, based on where they are written in the code (their lexical context), rather than where they are executed.

Consider this basic example:

function outerFunction() {
  let outerVariable = 'I am outside!';

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

const newFunction = outerFunction();
newFunction(); // Outputs: 'I am outside!'

Here, innerFunction has access to outerVariable, a variable declared in its outer scope (outerFunction). This is a classic example of lexical scoping: the inner function retains access to the outer function's variables.

Practical Example: Variable Scope in Nested Functions

Let's explore another example of block scope in nested functions:

function outer() {
  let movie = 'Amadeus';

  function inner() {
    let movie = 'The Shining';
    console.log(movie);
  }

  inner();
}

outer(); // The Shining

In this example, the outer() function is defined with a local variable movie. Within the outer() function, the inner() function is defined. This function also has a local variable called movie. When inner() is invoked, it logs the value of its local movie variable to the console, not the value of the outer movie variable from the outer() function scope.

This example demonstrates how variables declared in different scopes can have the same name without conflict. The inner() function has access to both the local movie variable declared in its scope, and the global movie variable declared in the outer scope.

The Scope Chain: Connecting Scopes

Every time a function is executed, a new scope is created for that function. This forms a hierarchy of scopes, known as the Scope Chain. The scope chain is a series of references to variable objects, where the JavaScript engine looks for variables.

When a variable is used, JavaScript starts at the innermost scope and searches outwards until it finds the variable it's looking for. If the variable is not found even in the global scope, a reference error is thrown.

Closure: A Function's Scope Chain

Closures in JavaScript are a distinctive feature of JavaScript. Think of a closure as a special kind of backpack that a function carries around. Whenever you create a function inside another function, the inner function gets a backpack that stores all the variables it might need from the outer function, even after the outer function has finished running.

A Closure is like a Memory Box: When you create a function inside another function, the inner function keeps a memory box (closure) of all the variables it needs from the outer function. This memory box stays with the inner function even when it's used outside of its original home.

Let's look at a basic example to see closures in action:

function outerFunction() {
  let outerVariable = 'I am outside!';

  function innerFunction() {
    // This is where the closure happens
    console.log(outerVariable); // It remembers outerVariable
  }
  
  // returns the function which has a closure over outerVariable
  return innerFunction; 
}

// Trying to access outerVariable here will not work
console.log(outerVariable); // ReferenceError

// But you can use innerFunction, which remembers outerVariable!
const newFunction = outerFunction();
newFunction(); // This will correctly log 'I am outside!'

In this example, innerFunction has a closure. It has the ability to remember and use outerVariable, which belongs to outerFunction, even when innerFunction is called outside of outerFunction. This makes closures a powerful tool in JavaScript for managing and preserving data in a specific context, for instance when dealing with asynchronous code such as event handling.

You'll come to asynchronous code and event handling in later lessons.

Utilizing Developer Tools

Navigating scopes can sometimes be tricky. This is where your browser's developer tools come in handy. Use the debugger keyword to inspect variable values and scopes in the browser, enhancing your understanding and debugging process.

Conclusion

In this lesson, you've explored the crucial concept of scope in JavaScript, which determines how variables and functions are accessible in your code. You've:

  • Differentiated between var, let, and const, discovering that var has function scope and is hoisted, while let and const have block scope and are not hoisted. This makes let and const more predictable and safer to use.
  • Learned that scope in JavaScript can be global or local, with local scope including both function scope and block scope.
  • Understood how nesting functions and loops create new scopes, and that variables with the same name can exist in different scopes without conflict.
  • Seen how block scope works with let and const, where variables are confined to the block they're defined in, such as within a loop or a conditional block.
  • Been introduced to closures, a powerful JavaScript feature where inner functions retain access to the variables of their outer functions, even after those outer functions have returned.

By understanding these concepts, you've laid a solid foundation for writing more efficient and error-free JavaScript code, especially when dealing with complex functions and nested structures.