JavaScript 函数声明和计算顺序

为什么这些例子中的第一个不起作用,而其他所有的都起作用?

// 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'); }
})();
20132 次浏览

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.

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

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.

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.