javascriptvariablesscopeconsole.loghoisting

why when using console.log() on a variable created afterwards but in another script tag i get an error instead of logging 'undefined' in the console?


why would this code snippet give an error in the console "Uncaught ReferenceError: x is not defined"

<body>
    <script>
        console.log(x);
    </script>
    
    <script>
        var x = 10;
    </script>
</body>

while this one logs "undefined"?

<body>
    <script>
        console.log(x);
        var x = 10;
    </script>
</body>

i was trying to learn about variable declaration and variable scopes. and expected that hoisting was going to happen because the whole code is in the same page. but because the console.log() is seperated in another scirpt tag i got an error instead of just logging 'undefined' in the console.


Solution

  • var is hoisted, that means it's accessible right in the beginning of the scope where it's defined even the declaration line could be in the end of the scope. If you access the var before it's declaration it's undefined because you still need to execute the declaration with possible initialization this variable to particular value. So your second example works that way.

    Read about hoisting here:

    https://developer.mozilla.org/en-US/docs/Glossary/Hoisting

    But in your first example 2 <scripts> have 2 different scopes, so the var basically doesn't exists in the first script thus the error not defined.

    Read about var and its scope here:

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var

    IMPORTANT I strongly advocate against using var. Use const and let instead. Using var with hoisting leads to bugs which sometimes are very hard to debug and fix. If you need using only var in your production, just downgrade your code with esbuild for example to the appropriate older version of JS.

    Interestingly enough, const and let are kinda hoisted also, but accessing them in the hoisted state leads to a runtime error (that's called a temporal dead zone), that's why they are more safe because you get an error immediately opposed to var hoisted silently and thus leaving you with a potential bug you don't know about.

    About the temporal dead zone:

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz

    <body>
        <script>
            console.log(x);
            const x = 10;
        </script>
    </body>

    An interesting part: the 2 <script>s are executed in order of appearance but if you change the order by deferring it (executing after DOM is built) with type="module" then you get the variable defined. This is only way to defer an inline script since defer wouldn't work:

    https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

    <body>
        <script type="module">
            console.log(x);
        </script>
        
        <script>
            var x = 10;
        </script>
    </body>