Storing values in variables is a fundamental concept in programming. The “scope” of a variable determines when it is available and unavailable throughout the program. Understanding variable scope in JavaScript is one of the keys to building a solid foundation in the language.

This article explains how JavaScript’s scoped system works. You’ll learn about different ways to declare variables, the difference between local and global scopes, and something called “promotion,” a JavaScript quirk that can turn a seemingly innocent variable declaration into a subtle error.

Variable scope

In JavaScript, the scope of a variable is controlled by the location of the variable declaration, which defines the part of the program that can access a particular variable.

Currently, there are three ways to declare variables in JavaScript: using the oldvar keyword, and using the newlet and const keywords. Before ES6, using the var keyword was the only way to declare variables, but now we can use letand const, which has stricter rules and code that is less error-prone. We’ll explore the differences between all three keywords below.

Scope rules vary from person to person. JavaScript has two scopes: global and local. There are two variations of local scopes: the old function scope and the new block scope introduced in ES6. It is worth noting that function scope is actually a special type of block scope.

The global scope

In scripts, the outermost scope is the global scope. Any variable declared within this scope becomes a global variable and can be accessed from anywhere in the program:

// Global Scope

const name = "Monique";

function sayHi() {
  console.log(`Hi ${name}`);
}

sayHi();
// Hi Monique
Copy the code

As this simple example shows, the variable name is a global variable. It is defined globally and can be accessed throughout the program.

However, while this may seem convenient, the use of global variables is discouraged in JavaScript. For example, this is because they might be overwritten by other scripts or elsewhere in the program.

The local scope

Any variables declared within a block belong to that particular block and become local variables.

The function in varJavaScript defines a range of const variables to use,let, and declare. Any variables declared in that function can only be accessed from that function and any nested function.

Code blocks (if,for, etc.) define scopes only for variables declared using the letandconst keyword. The var keyword is limited to function scope, which means that new scopes can only be created inside functions.

The letandConst keyword has block scope, which creates a new local scope for any blocks that declare them. You can also redefine individual code blocks in JavaScript that similarly delineate a scope:

{
  // standalone block scope
}
Copy the code

Function and block scopes can be nested. In this case, with multiple nested scopes, variables can be accessed in their own scope or from an internal scope. But outside its scope, the variable is not accessible.

A simple example to help visualize scopes

For clarity, let’s use a simple metaphor. Every country in our world has borders. Everything within these borders belongs to the state. Every country has many cities, and each city has its own city limits. Countries and cities are like JavaScript functions or blocks. They have their own local scope. The same is true across continents. Despite their size, they can also be defined as locales.

On the other hand, the world’s oceans cannot be defined as having local scope, since they actually encompass all local objects – continents, countries, and cities – and thus their scope is defined as global. Let’s visualize this in the next example:

var locales = { europe: function() { // The Europe continent's local scope var myFriend = "Monique"; var france = function() { // France country's local scope var paris = function() { // The Paris city's local scope console.log(myFriend); // output: Monique }; paris(); }; france(); }}; locales.europe();Copy the code

In this case, the myFriend variable can be obtained from the Paris function because it is defined in the outer scope of the France function. If we exchange the myFriend variable with the console statement, we get ReferenceError: myFriend is not defined because we cannot reach the inner scope from the outer scope.

Now that we know what local and global scopes are and how they are created, it’s time to learn how the JavaScript interpreter uses them to look up specific variables.

Going back to the given metaphor, suppose I want to find a friend named Monique. I knew she lived in Paris, so I started looking there. When I couldn’t find her in Paris, I went up one floor and expanded my search all over France. But once again, she wasn’t there. Next, I expanded my search again by going one step further. Finally, I found her in Italy, which in our case is the local dimension of Europe.

In the previous example, myFriend Monique had a variable for myFriend. In the last line we call the Europe () function, which calls France (), and finally when the Paris () function is called, the search begins. The JavaScript interpreter works from the currently executing scope until it finds the variable in question. If the variable is not found in any scope, an exception is thrown.

This type of lookup is called lexical (static) scoping. The static structure of the program determines the scope of the variables. The scope of a variable is defined by its location in the source code, and nested functions can access variables declared in their external scope. No matter where or even how a function is called from, its lexical scope depends only on where the function is declared.

Now let’s see how the new block scope works:

function testScope(n) {
  if (true) {
    const greeting = 'Hello';
    let name = n;
    console.log(greeting + " " + name); // output: Hello [name]
  }
  console.log(greeting + " " + name); // output: ReferenceError: greeting is not defined
}

testScope('David');   
Copy the code

In this example, we can see that the Andgreeting variable name declared with and is not accessible outside the block. constletif

Now let’s replace andconst and see what happens: letvar

function testScope(n) {
  if (true) {
    var greeting = 'Hello';
    var name = n;
    console.log(greeting + " " + name); // output: Hello [name]
  }
  console.log(greeting + " " + name); // output: Hello [name]
}

testScope('David');
Copy the code

As you can see, when we use the var keyword, the variable is accessible throughout the scope of the function.

In JavaScript, you can specify variables with the same name in multiple levels of nested scope. In this case, local variables take precedence over global variables. If you declare a local variable with the same name and a global variable, the local variable takes precedence when you use it in a function or block. This type of behavior is called shadowing. Simply put, internal variables overshadow external variables.

This is the exact mechanism that the JavaScript interpreter uses when trying to find a particular variable. It starts at the innermost range being executed at the time and continues until the first match is found, regardless of whether there are other variables with the same name in the outer level. Let’s look at an example:

var test = "I'm global";

function testScope() {
  var test = "I'm local";

  console.log (test);     
}

testScope();           // output: I'm local

console.log(test);     // output: I'm global
Copy the code

Even if the name is the same, local variables do not overwrite the global variable testScope() after the function is executed. But this is not always the case. Let’s consider:

var test = "I'm global";

function testScope() {
  test = "I'm local";

  console.log(test);     
}

console.log(test);     // output: I'm global

testScope();           // output: I'm local

console.log(test);     // output: I'm local (the global variable is reassigned)
Copy the code

This time, the local variable test overrides the global variable of the same name. When we run the code inside the testScope() function, the global variables are reassigned. If a local variable is assigned var without first declaring it with the keyword, it becomes a global variable. To avoid such bad behavior, you should always declare local variables before using them. Any variable declared with a keyword in a function var is a local variable. Declaring variables is considered a best practice.

Note: In strict mode, it is an error to assign a value to a variable without first declaring it.

lifting

The JavaScript interpreter performs many operations behind the scenes, one of which is “promotion.” If you don’t know about this “hiding” behavior, it can cause a lot of confusion. The best way to think about the behavior of JavaScript variables is to always visualize them as having two parts: declaration and initialization/assignment:

var state;             // variable declaration
state = "ready";       // variable assignment

var state = "ready";   // declaration plus assignment
Copy the code

In the above code, we first declare the variable state, and then we assign it the value “ready”. In the last line of code, we see that these two steps can be combined. But you need to remember that even though they look like one statement, the JavaScript engine actually treats that single statement as two separate statements, just like the first two lines of the example.

We already know that any variable declared in a scope belongs to that scope. But what we don’t know is that all variable declarations move to the top of their scope (global or local) regardless of where they are declared in a particular scope. This is called promotion because the variable declaration is promoted to the top of the scope. Note that promotion only moves the declaration. Any allocation is left in place. Let’s look at an example:

console.log(state);   // output: undefined
var state = "ready";
Copy the code

As you can see, when we record the value state, the output is undefined because we refer to it before the actual assignment. You might expect aReferenceError to be thrown because state has not yet been declared. But what you don’t know is that variables are undefined declared and initialized behind the scenes with default values. Here’s how the JavaScript engine interprets code:

var state;           // moved to the top
console.log(state);   
state = "ready";     // left in place
Copy the code

It is important to note that there is no physical movement of the variable. Lifting is just a model that describes what the JS engine is doing behind the scenes.

Now, let’s see how promotion works with the let variable:

{
  // Temporal dead one (TDZ) starts at the beginning of the scope
  console.log(state);   // output: "ReferenceError: Cannot access 'state' before initialization
  let state = "ready";  // end of TDZ. TDZ ends at actual variable declaration
}   
Copy the code

In this example, the console output is not undefined, but raises a reference error. Why is that? Let in contrast to variables, var variables cannot be read/written until they are fully initialized. They are fully initialized only where they are actually declared in the code. Therefore, the let variable declaration is promoted but not initialized with undefined, as is the case with the variable var. The part from the beginning of the block to the actual variable declaration is called the Temporal Dead Zone. This is a mechanism to ensure better coding practices by forcing you to declare a variable before you use it. If we move the console statement out of TDZ, we get the expected output: ready.

{
  // Temporal dead one (TDZ) starts at the beginning of the scope
  let state = "ready";  // end of TDZ. TDZ ends at actual variable declaration
  console.log(state);   // output: ready
} 
Copy the code

Variables declared with keywords have the same behavior let as const.

The function

Promotion also affects function declarations. But before we look at some examples, let’s understand the difference between a function declaration and a function expression:

function showState() {}          // function declaration
var showState = function() {};   // function expression
Copy the code

The easiest way to distinguish a function declaration from a function expression is to check the position of the word function in the statement. If function is the first thing in a statement, it is a function declaration. Otherwise, it is a functional expression.

Function declarations are completely enhanced. This means that the whole body of the function is moved to the top. This allows you to call a function before declaring it:

showState();            // output: Ready

function showState() {
  console.log("Ready");
} 

var showState = function() {
  console.log("Idle");
};
Copy the code

The above code works because the JavaScript engine moves the declaration of the showState() function and all its contents to the beginning of the scope. The code is explained as follows:

function showState() {     // moved to the top (function declaration)
  console.log("Ready");
} 

var showState;            // moved to the top (variable declaration)

showState();  

showState = function() {   // left in place (variable assignment)
  console.log("Idle");
};
Copy the code

You may have noticed that only function declarations are promoted, but function expressions are not. When assigning a function to a variable, the rules are the same as when the variable is promoted (only the declaration is moved, while the assignment remains in place).

In the above code, we see that function declarations take precedence over variable declarations. In the next example, we’ll see that when we have a function declaration and a variable assignment, the last one takes precedence:

var showState = function() {
  console.log("Idle");
};

function showState() {
  console.log("Ready");
} 

showState();            // output: Idle
Copy the code

This time, we call the function showState() on the last line of the code, which changes things. Now we have the output “Idle”. Here’s what it looks like when interpreted by the JavaScript engine:

function showState(){        // moved to the top (function declaration)
  console.log("Ready");
} 

var showState;               // moved to the top (variable declaration)

showState = function(){      // left in place (variable assignment)
  console.log("Idle");
};

showState();
Copy the code

Note: Arrow functions work in the same way as function expressions.

course

Class declarations are also promoted in a similar way to variables declared with let statements:

// Using the Person class before declaration var user = new Person('David', 33); // output: ReferenceError: Cannot access 'Person' before initialization // Class declaration class Person { constructor(name, age) { this.name = name; this.age = age; }}Copy the code

In this example, we can see that using the class for Person before declaring it produces a reference error similar to that in the let variable. To solve this problem, we must use the Person class after the declaration:

// Class declaration
class Person {
  constructor(name, age) {
    this.name = name; 
    this.age = age;
  }
}

// Using the Person class after declaration
var user = new Person('David', 33);
console.log(user); 
Copy the code

Classes can also be created using class expressions, usingvar, or let const declarations:

// Using the Person class console.log(typeof Person); // output: undefined var user = new Person('David', 33); // output: TypeError: Person is not a constructor // Class declaration using variable statement var Person = class { constructor(name, age) { this.name = name; this.age = age; }};Copy the code

In this example, we can see that the Person class is promoted to a function expression, but it cannot be used because its value is undefined. Again, to solve this problem, we must use the Person class after the declaration:

// Using the Person class console.log(typeof Person); // output: undefined // Class declaration using variable statement var Person = class { constructor(name, age) { this.name = name; this.age = age; }}; // Using the Person class after declaration var user = new Person('David', 33); console.log(user);Copy the code

Things to remember

  • The var variable is functional scoped.
  • Let and const variables are block-scoped (this also includes functions).
  • All declarations (classes, functions, and variables) are promoted to the top of the inclusion scope before any part of the code is executed.
  • First promote the function, then promote the variable.
  • Function declarations take precedence over variable declarations, but not over variable assignments.

If this article has been helpful to you, don’t forget to send me a triple ask, like, retweet, comment, and we’ll see you next time.

Collection is equal to white piao, praise is true feelings.

Dear friends, if you need JAVA interview documents, please click “like” and “forward”. After following me, I can get free information on 333

\