Scope and the Scope Chain

Scope and the Scope Chain

The scope of a variable is a region of our code that can access the variable.

There are three scopes in JavaScript: global scope, function scope, and block scope.

Types of Scope in JavaScript

1. Global Scope

The global scope is the scope that you are implicitly in when you start a program; it is outside any function or block.

Variables and functions declared in the global scope will be accessible to all scopes in your program and thus accessible everywhere in your code.

2. Function Scope

When you declare a function in your code, you create a new scope, called a function or local scope.

Variables, arguments, and functions declared within a function will only be accessible inside that function.

3. Block Scope

A block is a group of zero or more statements delimited by a pair of braces. The code structure below, while uncommon, is an example of how you can create a block of code.

let age = 54;

// opening brace starts a block
{
    const x = 24;
    console.log(x);
}
// closing brace ends the block

console.log(age);

The most common blocks used in JavaScript are if statements and loops.

Variables declared with let and const within a block will only be accessible inside that block.

In strict mode, functions declared within a block will only be accessible inside that block.

Scope in Practice

Consider the code below. Before looking at the solution, try to identify the different scopes present by drawing a circle around them.

'use strict';

const user = {
  username: 'Paul',
  birthYear: 1995,
  pets: ['dog', 'cat', 'hamster'],
};

function printUserInfo(user) {
  console.log(
    `My name is ${user.username} and I was born in ${user.birthYear}`
  );

  for (let i = 0; i < user.pets.length; i++) {
    console.log(`I own a ${user.pets[i]}`);
  }
}

{
  let x = 24;
  const y = 52;

  function printAge(user) {
    const age = new Date().getFullYear() - user.birthYear;
    console.log(age);
  }

  console.log(x);
  console.log(y);

  printAge(user);
}

printUserInfo(user);

Key of Scopes

Solution to Scope Practice

Stop and Think:

Can we call the printAge() function inside the printUserInfo() function?

Lexical Scope Vs. Dynamic Scope

Scope in JavaScript is lexical.

The term lexical pertains to how we write a piece of code. Lexical scope, therefore, means that we can determine the scope of a variable by looking at how we write our code. Variables in scope where we define a function or block are in scope in the function or block.

On the other hand, dynamic scope depends on the program execution, for example, when and where a function call occurs.

Let's look at the example code below. Using the knowledge gained thus far, can you anticipate what will happen when we run the code? (It might be helpful to draw a circle around the scopes you see).

let username = 'Paul';
let birthYear = 1995;

function print() {
  console.log(`My name is ${username} and I am ${age} years old`);
}

function getAge() {
  const age = new Date().getFullYear() - birthYear;
  print();
}

getAge();

markuphero-Q1TrS2WzkvW9pfeIaN2o-1 (1).png

Recall that scope in JavaScript is lexical, determined by how we write our code. Variables in scope where we define a function or block are in scope in the function or block.

When we define the print() function, the variables in scope are username, birthYear, and the function getAge(). The variable age is not in the scope of the print() function, such that, even if we call print() inside of getAge(), print() will never have access to the age variable.

The Scope Chain and Variable Masking

Scope is hierarchical; you can step into a new scope without losing access to the old scope, establishing a scope chain. The scope chain in a particular scope is equal to all the parent scopes' variable environments.

When a variable doesn't exist in the current scope, the JavaScript engine will lookup the scope chain until it finds the variable it's looking for in a process called variable lookup. Variable lookup occurs only one way, from the current scope to the parent scope.

let num = 52;

function doSomething() {
  if (num > 0) {
    console.log('I am a positive number');
  } else {
    console.log('I am a negative number');
  }
}

doSomething();

The variable num doesn't exist in the definition of the if block. Therefore, the JavaScript engine will perform a variable lookup by going up the scope chain into the doSomething() function in which the if block is ensconced. There is no definition for the variable num in the doSomething() function, so the JavaScript engine will go up the scope chain again into the global scope. It is in the global scope that the num variable is defined. This value is what the if block will use.

Stop and think:

What happens if we define variables with the same name in different scopes? Will we get an error?

let x = 54;

function foo() {
  let x = 100;
  console.log(x);
}

foo();

console.log(x);

In the example above, the variable x is defined in the global scope and again in the foo() function. The variable x in the foo() function is different from the one defined in the global scope. When we are in the foo() function, the variable x defined in the global scope is hidden and thus cannot be accessed. This phenomenon is called variable masking.