Tricky Concepts

JavaScript , Sat Dec 31 2022

    Tricky Concepts intro

    In the upcoming 3 sections we're going to cover some tricky concepts in JavaScript. Scope, Hoisting and Closures.

    First we're going to talk about the concept of Scope, a fundamental topic for any programming language. Then we're going to mention the concepts of hoisting and closures.

    I would dare to say that they are not all that useful in everyday coding. If you sticked to the good programming habits, you would never even encounter the use of hoisting.

    I've still decided to include the so called "tricky concepts" because questions regarding closures and hoisting are often asked in interview questions. I've got you covered! ;)

    Scope

    What is Scope? Why do we need it? And how can it help us write less error-prone code?

    Scope simply allows us to know where we have access to our variables. It shows us the accessability of variables, functions, and objects in some particular part of the code.

    Why would we want to limit the visibility of variables and not have everything availabile everywhere in our code?

    Firstly, it provides us with some level of security to our code. Secondly, it helps to improve efficiency, track bugs and reduce them. It also solves the problem of naming variables.

    We have three types of scopes:

    1. Global Scope
    2. Local Scope
    3. Block Scope (only with let and const)

    Variables defined inside a function are in local scope while variables defined outside of a function are in the global scope. Each function when invoked creates a new scope.

    There are rules about how scope works, but usually you can search for the closest { and } braces around where you define the variable. That "block" of code is its scope.

    Global Scope

    When you start writing in a JavaScript document, you're already in the Global scope.

    const name = 'Adrian';

    Variables written inside the Global scope can be accessed by and altered in any other scope.

    const logName = () => {
        console.log(name);
    }

    logName();
    Advantages of using Global variables

    • You can access the global variable from all the functions or modules in a program
    • It is ideally used for storing "constants" as it helps you keep the consistency.
    • A Global variable is useful when multiple functions are accessing the same data.

    Disadvantages of using Gsobas Variabses

    • Too many variables declared as global, then they remain in the memory till program execution is completed. This can cause of Out of :emory issue.
    • Data can be modified by any function. Any statement written in the program can change the value of the global variable. This may give unpred-ictable results in multi-tasking environment
    • If global variables are discontinued due to code refactoring, you will need to change all the modules where they are called.

    Local Scope

    Variables defined inside a function are in the local scope.

    // Global Scope

    const someFunction = () =>{
        // Local Scope #1

        const anotherFunction = () => {
            // Local Scope #2
    }
    }

    Advantages of using LocaI VariabIes

    • The use of local variables offer a guarantee that the values of variables will remain intact while the task is running
    • They are deleted as soon as any function is over and release the memory space which it occupies.
    • You can give local variables the same name in different functions because they are only recognized by the function they are declared in.

    Disadvantages of using Loca4 Variab4es

    • They have a very limited scope.

    This isn't necessarily a disadvantage, but if you ever find yourself needing to use that variable in a parent scope, just declare it there. Let's me use the above example to show you what I mean.

    If you need to use a variable only inside the anotherFunction function, just declare it there.

    // Global Scope

    const someFunction = () =>{
        // Local Scope #1

        const anotherFunction = () => {
            // Local Scope #2
    }
    }

    If for some reason, you need to use it both in the someFunction and anotherFunction functions, declare it in the someFunction.

    And if you need to use it everywhere across the file, declare it in the global scope.

    Block Scope

    if (true) {     // this 'if' conditional block doesn't create a scope

        // name is in the global scope because of the 'var' keyword
        var name = 'Adrian';
        // likes is in the local scope bacause of the 'let' keyword
        let likes = 'Coding';
        // skills is in the local scope bacause of the 'const' keyword
        let skills = 'Javavscript and PHP';
    }

    console.log(name); // logs Adrian
    console.log(likes); // Uncaught ReferenceError: likes is not defined
    console.log(skills); // Uncaught ReferenceError: skills is not defined

    If a variable or other expression is not "in the current scope," then it is unavailable for use.

    What is more useful?

    The local and global variables are equally important while writing a program in any programming language.

    However, a large number of the global variable may occupy a huge memory.

    An undesirable change to global variables is become tough to identify. Therefore, it is advisable to avoid declaring unwanted global variables. Always declare variables in the scope that you want to use them in.

    KEY DIFFERENCE

    • Local variable is declared inside a function whereas Global variable is declared outside the function.
    • Local variables are stored on the stack whereas the Global variable are stored on a fixed location decided by the compiler.
    • Local variable doesn't provide data sharing whereas Global variable provides data sharing.
    • Local variables are created when the function has started execution and is lost when the function terminates, on the other hand, Global variable is created as execution starts and is lost when the program ends.
    • Parameters passing is required for local variables whereas it is not necessar; for a global variable

    Hoisting

    What is hoisting?

    Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.

    This means that no matter where functions and variables are declared, they are moved to the top of their scope regardless of whether their scope is global or local.

    Basically, when Javascript compiles all of your code, all variable declarations using var are hoisted/lifted to the top of their functional (if declared inside a function) or to the top of their global scope (if declared outside of a function) regardless of where the actual declaration has been made. This is what we mean by “hoisting”.

    Variable Hoisting

    In JavaScript, an undeclared variable is assigned the value undefined at execution and is also of type undefined.

    console.log(typeof name); //undefined

    In JavaScript, a ReferenceError is thrown when trying to access a previously undeclared variable.

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

    Key thing to note in regards to hoisting is that the only thing that gets moved to the top is the variable declaration, not the actual value given to the variable.

    console.log(myString); // undefined
    var myString = 'test';
    var myString;
    console.log(myString); // undefined
    myString = 'test';

    Example 1:

    var hoist;
    console.log(hoist)
    hoist = 'The variable has been hoisted';

    Example 2:

    function hoist() {
        var message;
        console.log(meaasge);
        message = 'Hosting is cool!';
    } hoist(); // undefined
    Only declarations are hoisted

    JavaScript only hoists declarations, not initializations. If a variable is declared & initialized after using it, the value will be undefined.

    For example:

    console.log(num) // undefined
    var num;
    num = 6;

    To avoid this pitfall, we would make sure to declare and initialise the variable before we use it:

    function hoist() {
        message = 'Hosting is cool!';
        return message;
    }
    hoist(); // Hosting is cool!

    The variable declaration, var message whose scope is the function hoist(), is hoisted to the top of the function.

    This section of the eBook is the only time that you'll see me use the older syntax like function declarations and var keyword. Why is that?

    It shows you that newer versions of JS are trying to get away from this way of writing code.

    It's good to know that hoisting exists, but you should never actually use it. Always declare variables exactly where they should be: at the top of the scope they're used in. That way, your code is always going to be predictable, and you don't have to rely on hoisting.

    let and const hoist but you cannot access them before the actual declaration is evaluated at runtime. What does this mean? Let's see it in a simple example:

    console.log(myVarString); // undefined
    var myVarString = 'var';

    console.log(myLetString); // ReferenceError
    var myLetString = 'let';

    console.log(myConstString); // ReferenceError
    var myConstString = 'const';

    With let and const you get back exactly what you would expect: a reference error. And that's good. That's JavaScripts's way of letting us know that we need to write clean code.

    You should always declare variables before using them, it's common sense.

    Function Hoisting

    The same as var variables, the function declarations are hoisted completely to the top.

    hoisted(); // 'This function has been hoisted.'

    function hoisted() {
        console.log('This function has been hoisted.');
    }

    Again, would you ever need to do this? No. Always declare the function before you call it.

    Function expressions

    Another great thing, is that constants & function expressions save us from doing that. Function expressions (the more modern way of writing functions, with const keyword), are not hoisted.

    fuctionExpression() // ReferenceError

    const fuctionExpression = () =>{
        console.log('Will this work?');
    }

    Hoisting, as well as closures, which we're going to see next, are complex topics. I would say that they are not all that useful in everyday coding.

    Closures

    const outer = () => {
    const outerVar = 'Hello!';

    const inner = () => {
    const innerVar = 'Hi!';

    console.log(innerVar, outerVar);
    };

    return inner;
    };

    const innerFn = outer();

    innerFn();

    Normally, when you exit a function, all its variables “disappear”. This is because nothing needs them anymore. But what if you declare a function inside a function?

    Then the inner function could still be called later, and read the variables of the outer function.

    In practice, this is very useful! But for this to work, the outer function's variables need to "stick around" somewhere. So in this case, JavaScript takes care of "keeping the variables alive" instead of "forgetting" them as it would usually do. This is called a "closure".

    In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

    const init = () => {
    const hobby = 'Learning JavaScript!';

    const displayHobby = () => {
    console.log(hobby);
    };

    return displayHobby;
    };

    const myFunc = init();

    myFunc();

    Running this code has exactly the same effect as the previous example of the init() function above; what's different and interesting is that the displayHobby() inner function is returned from the outer function before being executed.

    At first glance, it may seem unintuitive that this code still works. In some programming languages, the local variables within a function exist only for the duration of that function's execution.

    Once init() has finished executing, you might expect that the name variable would no longer be accessible. However, because the code still works as expected, this is obviously not the case in JavaScript.

    The reason is that functions in JavaScript form closures. A closure is the combination of a function and the environment within which that function was declared.

    This environment consists of any local variables that were in-scope at the time the closure was created. In this case, myFunc is a reference to the instance of the function displayHobby created when init is run.

    The instance of displayHobby maintains a reference to its lexical environment, within which the variable name exists.

    For this reason, when myFunc is invoked, the variable name remains available for use and "Mozilla" is passed to alert.