4

I'm trying to figure out how this works. When I reference a named Javascript function that hasn't been declared yet, in some circumstances, it works. But if I use a function literal, it doesn't, but it also doesn't fail with a ReferenceError.

function works() {
    var works_ref = foo;
    function foo() {
        console.log('ok');
    };
    console.log('works ' + works_ref);
}

function fails() {
    var fails_ref = foo;
    var foo = function() {
        console.log('ok');
    };
    console.log('fails ' + fails_ref);
}

works();
fails();

This returns

"works function foo() {
            console.log('ok');
        }"
"fails undefined"

I'm wondering how the first example works—this is an interpreted language, not compiled, so I'd expect any sort of forward reference to fail—and why doesn't the second example generate a ReferenceError?

David Ehrmann
  • 7,366
  • 2
  • 31
  • 40
  • Declaration of variables go to the top, assignation of them stays in their place. So in works(), foo goes to the top, while in fails() the definition stays in order (thus, failing). I don't remember the name of this though, I'm sure someone will bring a better explanation. EDIT: [hoisting](http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html) (from the answers) – Francisco Presencia May 11 '15 at 16:22
  • @lightness-races-in-orbit This is definitely similar, but my question's about a much more specific behavior. – David Ehrmann May 11 '15 at 17:11
  • @DavidEhrmann: Your question is specifically answered by that one. There is no need to duplicate that content. The questions don't need to be lexically identical. – Lightness Races in Orbit May 11 '15 at 17:15
  • @LightnessRacesinOrbit Yes, by the time you reach halfway down the page. My question's a lot more specific, and you can tell it's a different question because of how different the answers are. I'm also approaching it more from the language/interpreter side than the programmer side. – David Ehrmann May 11 '15 at 17:55
  • @DavidEhrmann: The question asks "is there a difference between these two function declaration styles?" The answers are _all_ "yes, here it is". You've seen an oddity and want to know where it comes from: well, it comes from that. That question _explains_ the phenomenon you're asking about, nothing more nothing less. It's the same topic. There is no need to rehash it. The answers are not that different. Reaching halfway down the page is irrelevant. Dupe-closure is not a personal affront so you don't need to get defensive over it. :) – Lightness Races in Orbit May 11 '15 at 17:57

2 Answers2

5
function foo() {
    console.log('ok');
};

This is called function declaration. This will be processed at the compile time. So, JavaScript knows that there is a function called foo. That is why it assigns the function object here

var works_ref = foo;

In the second case,

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

foo is a variable, which is declared later in the function. So, because of hoisting,

var fails_ref = foo;

is aware of the fact that foo is defined somewhere in the function but it doesn't know the actual value of it. Because the value assignment var foo = function() {} happens at the runtime. That is why the default value undefined is used for foo till the actual assignment statement is executed.

thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • ... and the behavior which "thefourtheye" is describing – the "hoisting" – is very important, because, after all, there might be any number of decisions that your logic wants to make in order to choose *which* anonymous-function value to assign to the variable, `fails_ref`. In your `works()` example, JavaScript understands "at compile-time" the one-and-only thing that you mean. But in the `fails()` example, you're assigning an anonymous-function *value* to a variable, and that value (as far as JS knows or cares) "could have come from anywhere." – Mike Robinson May 11 '15 at 16:28
2

This is due to hoisting, what is actually happening, is this:

function works() {
    var works_ref = undefined;

    function foo() {
        console.log('ok');
    };

    works_ref = foo;

    console.log('works ' + works_ref);
}

function fails() {
    var fails_ref = undefined, 
        foo = undefined;

    fails_ref = foo; // <---------- note this line.

    foo = function() {
        console.log('ok');
    };

    console.log('fails ' + fails_ref);
}
epoch
  • 16,396
  • 4
  • 43
  • 71
  • 1
    Does JavaScript also hoist anonymous functions? (which means you would end up with something like this https://gist.github.com/IQAndreas/b1a213d1e0b5e3828cc4) – IQAndreas May 11 '15 at 16:25
  • 1
    Or is hoisting just an abstraction of what goes on under the hood when the code is interpreted by the VM? – IQAndreas May 11 '15 at 16:31