98

Today, I got completely surprised when I saw that a global variable has undefined value in a certain case.

Example:

var value = 10;
function test() {
    //A
    console.log(value);
    var value = 20;

    //B
    console.log(value);
}
test();

Gives output as

undefined
20

Here, why is the JavaScript engine considering global value as undefined? I know that JavaScript is an interpreted language. How is it able to consider variables in the function?

Is that a pitfall from the JavaScript engine?

Not A Bot
  • 2,474
  • 2
  • 16
  • 33
iamjustcoder
  • 4,714
  • 10
  • 33
  • 46

6 Answers6

185

This phenomenon is known as: JavaScript Variable Hoisting.

At no point are you accessing the global variable in your function; you're only ever accessing the local value variable.

Your code is equivalent to the following:

var value = 10;

function test() {
    var value;
    console.log(value);

    value = 20;
    console.log(value);
}

test();

Still surprised you're getting undefined?


Explanation:

This is something that every JavaScript programmer bumps into sooner or later. Simply put, whatever variables you declare are always hoisted to the top of your local closure. So, even though you declared your variable after the first console.log call, it's still considered as if you had declared it before that.
However, only the declaration part is being hoisted; the assignment, on the other hand, is not.

So, when you first called console.log(value), you were referencing your locally declared variable, which has got nothing assigned to it yet; hence undefined.

Here's another example:

var test = 'start';

function end() {
    test = 'end';
    var test = 'local';
}

end();
alert(test);

What do you think this will alert? No, don't just read on, think about it. What's the value of test?

If you said anything other than start, you were wrong. The above code is equivalent to this:

var test = 'start';

function end() {
    var test;
    test = 'end';
    test = 'local';
}

end();
alert(test);

so that the global variable is never affected.

As you can see, no matter where you put your variable declaration, it is always hoisted to the top of your local closure.


Side note:

This also applies to functions.

Consider this piece of code:

test("Won't work!");

test = function(text) { alert(text); }

which will give you a reference error:

Uncaught ReferenceError: test is not defined

This throws off a lot of developers, since this piece of code works fine:

test("Works!");

function test(text) { alert(text); }

The reason for this, as stated, is because the assignment part is not hoisted. So in the first example, when test("Won't work!") was run, the test variable has already been declared, but has yet to have the function assigned to it.

In the second example, we're not using variable assignment. Rather, we're using proper function declaration syntax, which does get the function completely hoisted.


Ben Cherry has written an excellent article on this, appropriately titled JavaScript Scoping and Hoisting.
Read it. It'll give you the whole picture in full detail.

Liam
  • 27,717
  • 28
  • 128
  • 190
Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
  • 34
    This is a good explanation. However, I miss a solution where you can access global variables inside a function. – DuKes0mE Dec 03 '14 at 10:28
  • 1
    @DuKes0mE - you can always access global variables inside a function. – Joseph Silber Dec 03 '14 at 14:51
  • 3
    yes, and why is case A from the opening post not working and saying its undefined ? I understand, that it will be interpreted as a local var instead of global, but how is it gonna be global then ? – DuKes0mE Dec 03 '14 at 14:54
  • 4
    to access global varaible use window.value – Venkat Reddy Mar 13 '15 at 23:02
  • 2
    when was this style of variable hoisting implemented? has this always been standard in javascript? – Dieskim Nov 22 '17 at 11:50
  • @DavidVanDeMeer - JS has worked like this ever since JS was a thing. – Joseph Silber Nov 27 '17 at 01:25
  • for some reason I find myself aware of hoisting, yet have this condition and can't see where the scoped variable is declared: ` function end() { var test = 0; iterate(some number){ test += 10; (NaN) } } ` – Elysiumplain Apr 13 '18 at 19:01
62

I was somewhat disappointed that the problem here is explained, but no one proposed a solution. If you want to access a global variable in function scope without the function making an undefined local var first, reference the var as window.varName

Alkanshel
  • 4,198
  • 1
  • 35
  • 54
  • 10
    Yeah, it sucks that the other guys didn't propose a solution because this is the first result in Google results. They did ALMOST answer the actual question asked about it being a pitfall in the js engine....sigh --> thanks for your answer – user1567453 Mar 01 '15 at 13:13
  • 2
    Thats the difference between theoretical knowledge and getting sh.. done. Thanks for that answer! – hansTheFranz Sep 01 '17 at 10:48
  • For me, it's a mistake that I 'var'd a global name anywhere in any function. Makes for confusion, at least. Makes for a need for a google search, at worst. Thanks – dcromley May 01 '18 at 17:58
  • 1
    Javascript keeps surprising me everyday that passes. Thanks man, the answer was helpful. – Raf Jun 19 '18 at 23:51
  • 1
    Thanks for the solution, I found this after a global var was undefined but only in Safari. Other 'include' files and global vars showed up such as 'google', so I copied the approach google used: window.globalVarFromJS = window.globalVarFromJS || {}; Then I found your solution so thought I would add to it. – Ralph Hinkley Dec 18 '18 at 14:47
12

Variables in JavaScript always have function-wide scope. Even if they were defined in the middle of the function, they are visible before. Similar phenomena may be observed with function hoisting.

That being said, the first console.log(value) sees the value variable (the inner one which shadows the outer value), but it has not yet been initialized. You can think of it as if all variable declarations were implicitly moved to the beginning of the function (not inner-most code block), while the definitions are left on the same place.

See also

Community
  • 1
  • 1
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
4

There is a global variable value, but when control enters the test function, another value variable is declared, which shadows the global one. Since variable declarations (but not assignments) in JavaScript are hoisted to the top of scope in which they are declared:

//value == undefined (global)
var value = 10;
//value == 10 (global)

function test() {
    //value == undefined (local)
    var value = 20;
    //value == 20 (local)
}
//value == 10 (global)

Note that the same is true of function declarations, which means you can call a function before it appears to be defined in your code:

test(); //Call the function before it appears in the source
function test() {
    //Do stuff
}

It's also worth noting that when you combine the two into a function expression, the variable will be undefined until the assignment takes place, so you can't call the function until that happens:

var test = function() {
    //Do stuff
};
test(); //Have to call the function after the assignment
James Allardice
  • 164,175
  • 21
  • 332
  • 312
0
  1. The simplest way to keep access to outer variables (not just of global scope) is, of course, to try to not re-declare them under the same name in functions; just do not use var there. The use of proper descriptive naming rules is advised. With those, it will be hard to end up with variables named like value (this aspect is not necessarily related to the example in the question as this variable name might have been given for simplicity).

  2. If the function might be reused elsewhere and hence there is no guarantee that the outer variable actually defined in that new context, Eval function can be used. It is slow in this operation so it is not recommended for performance-demanding functions:

    if (typeof variable === "undefined")
    {
        eval("var variable = 'Some value';");
    }
    
  3. If the outer scope variable you want access to is defined in a named function, then it might be attached to the function itself in the first place and then accessed from anywhere in the code -- be it from deeply nested functions or event handlers outside of everything else. Notice that accessing properties is way slower and would require you to change the way you program, so it is not recommended unless it is really necessary: Variables as properties of functions (JSFiddle):

    // (the wrapper-binder is only necessary for using variables-properties
    // via "this"instead of the function's name)
    var functionAsImplicitObjectBody = function()
    {
        function someNestedFunction()
        {
            var redefinableVariable = "redefinableVariable's value from someNestedFunction";
            console.log('--> functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
            console.log('--> redefinableVariable: ', redefinableVariable);
        }
        var redefinableVariable = "redefinableVariable's value from someFunctionBody";
        console.log('this.variableAsProperty: ', this.variableAsProperty);
        console.log('functionAsImplicitObjectBody.variableAsProperty: ', functionAsImplicitObjectBody.variableAsProperty);
        console.log('redefinableVariable: ', redefinableVariable);
        someNestedFunction();
    },
    functionAsImplicitObject = functionAsImplicitObjectBody.bind(functionAsImplicitObjectBody);
    functionAsImplicitObjectBody.variableAsProperty = "variableAsProperty's value, set at time stamp: " + (new Date()).getTime();
    functionAsImplicitObject();
    
    // (spread-like operator "..." provides passing of any number of arguments to
    // the target internal "func" function in as many steps as necessary)
    var functionAsExplicitObject = function(...arguments)
    {
        var functionAsExplicitObjectBody = {
            variableAsProperty: "variableAsProperty's value",
            func: function(argument1, argument2)
            {
                function someNestedFunction()
                {
                    console.log('--> functionAsExplicitObjectBody.variableAsProperty: ',
                        functionAsExplicitObjectBody.variableAsProperty);
                }
                console.log("argument1: ", argument1);
                console.log("argument2: ", argument2);
                console.log("this.variableAsProperty: ", this.variableAsProperty);
                someNestedFunction();
            }    
        };
        return functionAsExplicitObjectBody.func(...arguments);
    };
    functionAsExplicitObject("argument1's value", "argument2's value");
    
DDRRSS
  • 306
  • 3
  • 8
0

I was running into the same problem even with global variables. My problem, I discovered, was global variable do Not persist between html files.

<script>
    window.myVar = 'foo';
    window.myVarTwo = 'bar';
</script>
<object type="text/html" data="/myDataSource.html"></object>

I tried to reference myVar and myVarTwo in the loaded HTML file, but received the undefined error. Long story/day short, I discovered I could reference the variables using:

<!DOCTYPE html>
<html lang="en">
    <!! other stuff here !!>
    <script>

        var myHTMLVar = this.parent.myVar

        /* other stuff here */
    </script>
</html>
Nathan Sutherland
  • 1,191
  • 7
  • 9