JavaScript: Let's talk about Scopes and what is a Closure

JavaScript: Let's talk about Scopes and what is a Closure

ยท

6 min read

One of the most important things as a novice that you have to understand are scopes.

On my mission to help web developers understand JavaScript better I have decided to clarify the notion around scopes and closures.

Learn by example

Before we go any further let's first try to run some code.

We define a simple variable and try to access its value:

var foo = 'bar';
console.log(foo); // 'bar'

Nothing special here. Let's now try to declare the same variable inside a function:

function exampleFunction() {
    var foo = 'bar';
}
console.log(foo);

Now suddenly our output will be:

// Output
'Uncaught ReferenceError: foo is not defined'

Why does JavaScript decide to throw hands in this case? ๐Ÿค”

In this case the function creates a scope for our foo variable and in this case can only be accessed from within it. So, what can we learn from this?

Scope is the accessibility of variables, functions and objects during the runtime. In simpler words it's the visibility of resources in different parts of your code.

What is the purpose of this?

The question arises. Why would I not be able to access resources from my code wherever I want? Scopes provide a level of security to the code you write. JavaScript only provides the user with the resources they need at a certain time.

Let's think about this blog article. It was written by me but what if there were other users that could edit my article and one day I would see someone has edited my article and introduced some typos. I would not be able to tell which one of the users of my blog edited my article. Suddenly I realize that I should create some user roles and only assign privileges when they are needed to prevent such errors. In programming languages this is implemented by scopes and we will discuss them next.

JavaScript Scopes

JavaScript implements two types of scopes:

  • Global scope
  • Local scope

Functions create the local scope while everything defined outside of a function are inside the global scope.

Global Scope

When you first write your first line of code inside a javascript file you are writing inside the global scope. Variables defined inside the global scope are known as global variables and can be accessed by any local scope.

var foo = 'bar';

console.log(foo); // 'bar'

function localScope() {
    console.log(foo); // 'bar'
}

Local Scope

Variables declared inside functions live inside the local scope and therefore are known as local variables. A local scope is created every time a function is declared, so you can use variables with the same name in different functions (each call of that function creates new instances of those variables). You can't access variables from a local scope from the global scope or from another local scope.

function firstLocalScope() {
    var foo = 'bar';
    console.log(foo); // 'bar'
    function innerLocalScope() {
        var foo = 'baz';
        console.log(foo); // 'baz'
    }
}

function secondLocalScope() {
    var foo = 'foobar';
    console.log(foo); // 'foobar'
}

As you can see above we have defined three local scopes. Each has a foo variable defined and each console.log prints out the corresponding value.

Block statements

Block statements are used to group zero or more statements. It is delimited by curly brackets {} and may optionally be labelled. Blocks are usually defined by if..else, switch, try..catch or loops like for or while.

Variables defined inside blocks are not part of a new local scope. They remain inside the same scope of where the block was defined.

if (true) {
    var foo = 'bar';
}

console.log(foo); // 'bar'

The variable foo can be accessed even outside the if block.

In 2015 JavaScript released a revision known as ES6 or ECMAScript 6 that introduced the let and const keywords. These new keywords support the creation of local scopes inside block statements.

// global scope

if (true) {
    // this block statement does not create a local scope

    var foo = 'foo'; // global scope

    let bar = 'bar'; // local scope

    const baz = 'baz'; // local scope
}

console.log(foo); // 'foo'
console.log(bar); // Uncaught ReferenceError: bar is not defined
console.log(baz); // Uncaught ReferenceError: baz not defined

Module scope

The ECMAScript 6 revision also brought us modules. I will not dive into how modules work as this is not part of this article but if you want to read more you can do so here.

Let's define our module:

// square.js

const sides = 4;

console.log(sides); // 4

Now let's import it:

import "./square.js"

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

As you can see sides is not defined as it does not exist outside the module scope. Every variable inside our module becomes encapsulated within it (unless explicitly made public with the export keyword).

Lexical scope

Lexical scope is what we have when a nested scope has access to outer scopes (does not work the other way around). In the case of inner functions the children have access to their parents' resources. Lexical scope is also known as Static Scope.

Let's take the example of nested functions:

var globalVar = 'foo';
function parentFunc() {
    var parentVar = 'bar';
    var sameName = 'foobar';

    console.log(childVar); // Uncaught ReferenceError parentVar is not defined

    function childFunc() {
        var childVar = 'baz';
        var sameName = 'foobaz';

        console.log(globalVar); // 'foo'
        console.log(parentVar ); // 'bar'
        console.log(childVar ); // 'baz'
        console.log(sameName); // 'foobaz' innermost scope takes precedence
    }

    childFunc();
}

parentFunc();

Closures

Closures are related to lexical scopes. Let's make some adjustments to our previous example and simplify it a bit:

function outerScope() {
    var outerVar = 'foo';

    return function innerScope() {
        console.log('My parent\'s value is ' + outerVar);
    }
}

const myInnerScope = outerScope(); // the function returned from outerScope is saved inside 'myInnerScope'

myInnerScope(); // 'My parent's value is foo'

// we can avoid creating another variable by calling the function twice with '()()'
outerScope()();

As you can see when calling myInnerScope we still have access to the variable outerVar. A closure is created when an inner function acceses the outer scope of its outer function.

The important thing to notice is that even if the function has been returned it still has access to its outer scope. You can also see it as the inner function closing upon its parent making the resources available.

Conclusion

It is important to understand scopes and closures if you want to dive in and learn more advanced JavaScript topics. I hope I have been of help for you in the learning process of these concepts.

If anything is still unclear you can leave me a comment and I will try to clarify it better for you. ๐Ÿ‘‡

Did you find this article valuable?

Support Toader Daniel by becoming a sponsor. Any amount is appreciated!

ย