JavaScript function declaration and evaluation order
Asked Answered
W

4

90

Why does the first one of these examples not work, but all the other ones do?

// 1 - does not work
(function() {
setTimeout(someFunction1, 10);
var someFunction1 = function() { alert('here1'); };
})();

// 2
(function() {
setTimeout(someFunction2, 10);
function someFunction2() { alert('here2'); }
})();

// 3
(function() {
setTimeout(function() { someFunction3(); }, 10);
var someFunction3 = function() { alert('here3'); };
})();

// 4
(function() {
setTimeout(function() { someFunction4(); }, 10);
function someFunction4() { alert('here4'); }
})();
Whoso answered 8/10, 2010 at 3:5 Comment(0)
Z
199

This is neither a scope problem nor is it a closure problem. The problem is in understanding between declarations and expressions.

JavaScript code, since even Netscape's first version of JavaScript and Microsoft's first copy of it, is processed in two phases:

Phase 1: compilation - in this phase the code is compiled into a syntax tree (and bytecode or binary depending on the engine).

Phase 2: execution - the parsed code is then interpreted.

The syntax for function declaration is:

function name (arguments) {code}

Arguments are of course optional (code is optional as well but what's the point of that?).

But JavaScript also allows you to create functions using expressions. The syntax for function expressions are similar to function declarations except that they are written in expression context. And expressions are:

  1. Anything to the right of an = sign (or : on object literals).
  2. Anything in parentheses ().
  3. Parameters to functions (this is actually already covered by 2).

Expressions unlike declarations are processed in the execution phase rather than the compilation phase. And because of this the order of expressions matter.

So, to clarify:


// 1
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();

Phase 1: compilation. The compiler sees that the variable someFunction is defined so it creates it. By default all variables created have the value of undefined. Note that the compiler cannot assign values yet at this point because the values may need the interpreter to execute some code to return a value to assign. And at this stage we are not yet executing code.

Phase 2: execution. The interpreter sees you want to pass the variable someFunction to setTimeout. And so it does. Unfortunately the current value of someFunction is undefined.


// 2
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();

Phase 1: compilation. The compiler sees you are declaring a function with the name someFunction and so it creates it.

Phase 2: The interpreter sees you want to pass someFunction to the setTimeout. And so it does. The current value of someFunction is its compiled function declaration.


// 3
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();

Phase 1: compilation. The compiler sees you have declared a variable someFunction and creates it. As before, its value is undefined.

Phase 2: execution. The interpreter passes an anonymous function to setTimeout to be executed later. In this function it sees you're using the variable someFunction so it creates a closure to the variable. At this point the value of someFunction is still undefined. Then it sees you assigning a function to someFunction. At this point the value of someFunction is no longer undefined. 1/100th of a second later the setTimeout triggers and the someFunction is called. Since its value is no longer undefined it works.


Case 4 is really another version of case 2 with a bit of case 3 thrown in. At the point someFunction is passed to setTimeout it already exists due to it being declared.


Additional clarification:

You may wonder why setTimeout(someFunction, 10) doesn't create a closure between the local copy of someFunction and the one passed to setTimeout. The answer to that is that function arguments in JavaScript are always, always passed by value if they are numbers or strings or by reference for everything else. So setTimeout does not actually get the variable someFunction passed to it (which would have meant a closure being created) but rather only gets the object that someFunction refers to (which in this case is a function). This is the most widely used mechanism in JavaScript for breaking closures (for example in loops) before the invention of the let keyword.

Zither answered 8/10, 2010 at 3:57 Comment(17)
This is probably a lack of understanding of closures, but I always thought of it as access to an scope, not as creating something between one scope and another. I also thought it was at the scope level, not the variable level. Would you mind either elaborating a bit more on that, or pointing me in the direction of something I can read up on? Again, great answer, wish I could vote it up twice.Billfold
This answer makes me wish I could vote up multiple times on the same answer. Truly a great answer. ThanksIsoelectronic
@Matt: I've explained this elsewhere (several times) on SO. Some of my favourite explanation: #3572980Zither
@Matt: Also: https://mcmap.net/q/16683/-hidden-features-of-javascript-closed/…Zither
@Matt: Technically, closures involve not scope but stack frame (otherwise known as activation record). A closure is a variable shared between stack frames. A stack frame is to scope what an object is to class. In other words, a scope is what the programmer perceives in the code structure. A stack frame is what is created at runtime in memory. It's not really like that but close enough. When thinking about runtime behavior, a scope-based understanding is sometimes not enough.Zither
@slebetman: That totally makes sense, thank you :) Building on your analogy, a closure would be a private static variable (in java terminology), right? I guess this is what I get by learning about them from languages like javascript and ruby rather then a real functional language.Billfold
Thanks. Edited the question title and tags to reflect the fact that this isn't really about closures.Whoso
@Zither for your explanation of example 3, you mention that the anonymous function within setTimeout creates a closure to the someFunction variable and that at this point, someFunction is still undefined - which makes sense. It seems like the only reason example 3 doesn't return undefined is because of the setTimeout function (the delay of 10 milliseconds allows JavaScript to execute the next assignment statement to someFunction, thus making it defined) right?Buroker
You say: name and arguments are of course optional in your function definition explanation. Now that's confusing.. MDN, MSDN, ES3, ES5 and all books I've read, contradict that statement and say that only arguments are optional in a function declaration: function Identifier ( FormalParameterList opt ){ FunctionBody } (so identifier is mandatory) but FunctionExpression : function Identifier opt ( FormalParameterList opt ){ FunctionBody } So, what am I missing?Ferrite
@GitaarLAB: Without an identifier it is still legal only I believe it's compiled as an expression instead of a declaration. No browser to date will generate an error if the result of an expression is discarded (not assigned using the = operator)Zither
@GitaarLAB: When I wrote my answer I based it not on any documentation or spec but on a test script I tested on IE, FF, Chrome and Opera. None of the browsers I tested generated any error message when I declared a function without an identifier.Zither
@GitaarLAB: Just tested it on latest version of Chrome and it appears that it now errors out if the identifier is not given. So my answer appears to be out of date. I'll fix it.Zither
@slebetman: Thanks for this! Do you have a reference, perhaps to a browser engine documentation, for the two phases? I've been trying to get more information about the compilation phase and exactly what happens during this phase and have not had much luck finding good explanations. Thanks!Starkey
In phase 1, no compilation takes place. Can you please rename that to something more fitting, like "call context initialisation", "stack frame setup" or "scope creation"?Berwickupontweed
@Berwickupontweed Stack frame setup happens after phase 1. If phase 1 is stack frame setup then what I mean is phase 0 - compilation. The only modern language I know that has no compilation phase are the shell languages - bash, sh etc. Even Tcl have had compilation since the early 2000s. Javascript has been a bytecode language since Eich's first implementation (of course, the original syntax looked more like Lisp but that version was never released)Zither
@Berwickupontweed Perhaps if you don't like "compilation" you can think of it as "parsing+bytecode generation" but in CS parsing + bytecode generation is usually called "compilation"Zither
@Zither Ok, but if you actually mean phase 0 then the answer is wrong. "Expressions unlike declarations are processed in the execution phase rather than the compilation phase." - expressions are (need to be) parsed (and converted to bytecode) in the first pass as well. "The compiler sees you have declared [something] and creates it" - but that doesn't happen in phase 0, it happens in phase 1, when the scope is initialised, when the function is called. The code is compiled only once, the variables are created as often as necessary.Berwickupontweed
W
2

Javascript's scope is function based, not strictly lexical scoping. that means that

  • Somefunction1 is defined from the start of the enclosing function, but it's content is undefined until assigned.

  • in the second example, the assignment is part of the declaration, so it 'moves' to the top.

  • in the third example, the variable exist when the anonymous inner closure is defined, but it's not used until 10 seconds later, by then the value has been assigned.

  • fourth example has both of the second and third reasons to work

Weeny answered 8/10, 2010 at 3:12 Comment(0)
L
1

Because someFunction1 has not yet been assigned at the time the call to setTimeout() is executed.

someFunction3 may look like a similar case, but since you are passing a function wrapping someFunction3() to setTimeout() in this case, the call to someFunction3() is not evaluated until later.

Larissa answered 8/10, 2010 at 3:7 Comment(4)
But someFunction2 has not yet been assigned when the call to setTimeout() is executed either...?Whoso
@jnylen: Declaring a function with the function keyword is not precisely equivalent to assigning an anonymous function to a variable. Functions declared as function foo() are "hoisted" to the beginning of the current scope, while variable assignments occur at the point where they are written.Architectonic
+1 for functions being special. However just because it can work doesn't mean it should be done. Always declare before you use.Kingcup
@mway: in my case I've organized my code within a "class" into sections: private variables, event handlers, private functions, then public functions. I need one of my event handlers to call one of my private functions. For me, keeping the code organized in this way wins out over ordering the declarations lexically.Whoso
W
1

This sounds like a basic case of following good procedure to stay out of trouble. Declare variables and functions before you use them and declare functions like this:

function name (arguments) {code}

Avoid declaring them with var. This is just sloppy and leads to problems. If you get into the habit of declaring everything before using it, most of your problems will disappear in a big hurry. When declaring variables, I would initialize them with a valid value right away to insure that none of them are undefined. I also tend to include code that checks for valid values of global variables before a function uses them. This is an additional safeguard against errors.

The technical details of how all this works is sort of like the physics of how a hand grenade works when you play with it. My simple advice is to not play with hand grenades in the first place.

Some simple declarations at the beginning of the code might solve most most of these kinds of problems, but some cleanup of the code might still be necessary.

Additional Note:
I ran a few experiments and it seems that if you declare all of your functions in the manner described here, it doesn't really matter what order they are in. If function A uses function B, function B does not have to be declared before function A.

So, declare all of your functions first, your global variables next, and then put your other code last. Follow these rules of thumb and you can't go wrong. It might even be best to put your declarations in the head of the web page and your other code in the body to ensure enforcement of these rules.

Wolcott answered 24/7, 2012 at 23:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.