JavaScript: Scope in Plain Words

Good day, friends!



Scope is an important concept in determining the accessibility of variables. This concept is at the heart of closures, dividing variables into global and local.



In this article I will try to explain in simple terms what scope is in JavaScript.



1. Scope



Before diving into the details of scope, let's look at a quick example.



Let's say we have defined a variable:



const message = 'Hello'
console.log(message) // 'Hello'


We can easily output its value to the console. This is clear.



Now we place the declaration of the message variable in the if block:



if (true) {
    const message = 'Hello'
}
console.log(message) // ReferenceError: message is not defined


This time, when trying to access the variable, a ReferenceError: message is not defined exception is thrown.



Why did this happen?



Because the if block has created a scope for the message variable. And message is only available within this scope.







Thus, the availability of variables is limited by the scope in which they are defined.



So the scope is the scope of the variables.



2. Block scope



A block of code in JavaScript defines the scope of variables declared with the const and let keywords:



if (true) {
    //    if
    const message = 'Hello'
    console.log(message) // 'Hello'
}
console.log(message) // ReferenceError


The first console.log () safely prints the value of the message variable to the console, since this variable is accessed in the scope in which it is defined.



However, calling the second console.log () throws an error because the message variable is not available in its external scope: message does not exist in the current context.



Block scope is also created in if, for, while statements.



For instance:



for (const color of ['green', 'red', 'blue']) {
    //    for
    const message = 'Hi'
    console.log(color) // 'green', 'red', 'blue'
    console.log(message) // 'Hi', 'Hi', 'Hi'
}
console.log(color) // ReferenceError
console.log(message) // ReferenceError


The color and message variables only exist inside the for block.



The same is true for the while statement:



while (/*  */) {
    //    while
    const message = 'Hi'
    console.log(message) // 'Hi'
}
console.log(message) // ReferenceError


The message defined in while is only available within this loop.



In JavaScript, you can create self-contained blocks of code. They also define their own scope:



{
    const message = 'Hi'
    console.log(message) // 'Hi'
}
console.log(message) // ReferenceError


2.1. var is not block-scoped



As we saw in the previous examples, a block of code creates scope for variables declared with the const and let keywords. However, this does not work for variables declared with the var keyword.



Let's consider an example:



if (true) {
    //    if
    var count = 0
    console.log(count) // 0
}
console.log(count) // 0


The variable count is available inside the if block, as expected. However, it is also available outside this block!



This is because the code block does not create scope for variables declared with the var keyword. But the function does it.



3. Function scope



Functions in JavaScript create scope for all variables, no matter what keyword they are declared with (var, const, or let).



For instance:



function run() {
    //    run()
    var message = ', , !'
    console.log(message)
}
run() // ', , !'
console.log(message) // ReferenceError


The run () function creates a scope. The message variable is available inside the function, but not outside.



Likewise, the function creates scope for variables declared with const and let, and even for other functions and function expressions:



function run() {
    //    run()
    const two = 2
    let one = 1
    function run2() {}
    var run3 = () => {}

    console.log(two)
    console.log(one)
    console.log(run2)
    console.log(run3)
}
run() // 2 1 ƒ run2() {} () => {}
console.log(two) // ReferenceError
console.log(one) // ReferenceError
console.log(run2) // ReferenceError
console.log(run3) // ReferenceError


4. Module visibility



ES6 modules also create scope for variables, functions and classes.



The circle module creates a constant pi (for internal use):



//    circle
const pi = 3.14

console.log(pi) // 3.14

//  pi


The pi variable is declared inside the circle module and is not exported from there.



Then the circle module is imported:



import './circle'

console.log(pi) // ReferenceError


The variable pi is not available outside of the circle module (until it is exported using export).



Modular scope encapsulates modules. This means that private variables (which are not exported) are used for the module's own needs and are protected from outside access.



Thus, we can say that scope is an encapsulation mechanism for blocks of code, functions and modules.



5. Scopes can be nested



An interesting feature of scopes is that they can be nested within one another.



In the following example, the run () function creates a scope, and inside it, an if block creates another scope:



function run() {
    //    run()
    const message = ', , !'

    if (true) {
        //    if
        const friend = ''
        console.log(message) // ', , !'
    }

    console.log(friend) // ReferenceError
}
run()


The scope of the if block is nested within the scope of the run () function.



A scope that is inside another scope is called an internal scope. In the above example, this is the scope of the if block.



A scope that contains another scope is called an outer scope. In the above example, this is the scope of the run () function.







What about variable availability? There is a simple rule to remember:



Variables from the outer scope are available in the inner scope.



Therefore, the message variable is available inside the if block.



6. Global scope



The global scope is the outermost scope. It is available for any internal or local scope. In a browser, the global scope is created when the JavaScript file specified in the src attribute of the script tag is loaded:



<script src="script.js">




// script.js

// global scope

let counter = 1



Variables declared in the global scope are global variables. They are available in any other area.



Global scope is a mechanism that allows the JavaScript runtime (browser, Node.js) to expose host (i.e., environment-owned) objects to applications as global variables.



For example, window and document are global variables (objects) provided by the browser. In Node.js, such a variable is, for example, the process object.



7. Lexical scope



Let's define two functions, one of which is nested within the other:



function outer() {
    //    outer()
    let v = '     outer()!'

    function inner() {
        //    inner()
        console.log(v) // '     outer()!'
    }

    return inner
}

const f = outer()
f()


Take a look at the last line: inner () is called outside the scope of outer (). How does JavaScript know that the value printed to the console in the inner () function belongs to the variable v declared in the outer () function?



Answer: thanks to lexical scoping.



JavaScript implements a mechanism called lexical or static scoping. Lexical scope means that the accessibility of variables is statically determined by the position of these variables within the scope of the nested function: variables from the scope of the outer function are available in the scope of the nested function.



The formal definition of lexical scope is as follows:



The lexical scope consists of statically defined external scopes, i.e. from outer regions, fixed by using variables from those regions in inner functions.



In the above example, the lexical scope of the inner () function consists of the scope of the outer () function.



Moreover, inner () is a closure because it uses the value of the variable from the lexical scope.



8. Isolation of variables



Obviously, the scope isolates the variables. This allows different scopes to contain variables with the same name.



You can use variables count, index, current, value, etc. in different areas without the threat of collisions (name conflicts).



For instance:



function foo() {
    //    foo()
    let count = 1
    console.log(count) // 1
}

function bar() {
    //    bar()
    let count = 2
    console.log(count) // 2
}

foo()
bar()


Conclusion



Scope determines the availability of variables. A variable declared in the current scope is available only within it.



In JavaScript, scopes are created by blocks, functions, and modules.



Variables declared with the const and let keywords can be block, functional, or modular, while variables declared with the var keyword are not block-scoped.



Scopes can be nested. Variables declared in the outer scope are available in the inner scope.



Lexical scope consists of statically defined outer scopes. Any function, regardless of where it is executed, has access to variables from its lexical scope (this is the essence of closures).



I hope the article was useful to you. Thank you for attention.



All Articles