Search code examples
javascriptfunctionhoisting

How to use variable hoisting with function expression in Javascript?


I understand that variable hoisting is the behavior of moving declaration to the top of the function or global context. This is shown in the code below:

console.log(a);
var a = 'Hello World!';

The above code actually gets the following behavior:

var a;
console.log(a);
a = 'Hello World!';

The resulting output from the above code is undefined.

In above example, it is important to note that variable hoisting moves only the declaration part at the top. The initialization part is not moved at the top.

When I am trying to replicate the same behavior using function expression, I am getting an unexpected output.

This is shown in code below:

function sayHello() {
    var say = function() { console.log(hello); }
    var hello= 'Hello World !!';
    return say;
}
sayHello()(); // output Hello World !!

I am trying to understand why we have not received undefined as an output because the variable hello is initialized only after it is used (similar to the previous example).


Solution

  • The behavior you see comes from how closures work. It's not that the value of hello somehow gets hoisted, it's that the code that reads the value of hello (the console.log(hello) part) is only executed after you set the value!

    To not get confused here with the hoisting, since that is not really relevant to what you are seeing, let's write out the code in the way it would look after hoisting:

    function sayHello() {
        var hello;
        var say = function() { console.log(hello); }
        hello = 'Hello World !!';
        return say;
    }
    sayHello()(); // output Hello World !!
    

    If you would insert a say() before the line hello = 'Hello World !!'; it would print undefined.

    Now, when you have code like this...

        var hello;
        var say = function() { console.log(hello); }
    

    ...then the hello inside say is not a copy of the outside hello, it's actually the same variable you are referencing here. And it continues existing even after the outer functioned returned, because now you have a reference to the say function which in turn still keeps a reference to hello.

    I am trying to understand why we have not received undefined as an output because the variable hello is initialized only after it is used (similar to the previous example).

    I think this is your misunderstanding. It is used when you call the say function at the very end - chronologically speaking. The fact that looking at the code from top to bottom hello appears to be "used" before doesn't matter, because that's not the order in which the code executes. At the line var say = function () { console.log(hello); } you just do var say = something;, the something doesn't have any meaning at that point (it just has to be syntactially correct). The access to hello occurs only when you call say at the end.

    (You could also try with something like var say = function () { derp(); }, and you'd notice that you get the error derp is not defined only when you call that function, not when you define it.)

    So, chronologically, this is what happens:

    • sayHello is called
    • hello is created and contains undefined
    • say is created and assigned a function expression with body console.log(hello)
    • hello is assigned "Hello World !!"
    • sayHello returns say
    • The function expression (which was say) is called
    • console.log(hello) executes
      • Remember that hello contains "Hello World !!" at that point
    • The string "Hello World !!" gets printed

    You can expand this example, to help understanding:

    function sayHello() {
      var hello;
      var say = function() { console.log(hello); }  
      hello = 'Hello World !!';
      var think = function() { hello = 'What should I think about?'; }
      return [say, think];
    }
    
    var [say, think] = sayHello();
    say(); // Prints "Hello World !!";
    think();
    say(); // prints "What should I think about?"
    

    As you can see, hello just lives on and can be used by both say and think, even after sayHello returned, all of these parts of the code reference the same hello variable.