JavaScript Function Declaration and Evaluation Order

JavaScript function declaration and evaluation order

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.

Does function declaration order matter in ES6?

This has to do with ECMAScript's way of evaluation and initialization. In your first snippet, where todoApp shows up before the other functions:

export const todoApp = (state = {}, action) => {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
}
}

The reason why this does not throw a ReferenceError is because the return value is evaluated after todos and visibilityFilter is defined. This is described in the ECMAScript 2018 Specification:

9.2.12 FunctionDeclarationInstantiation ( func, argumentsList )

NOTE 1 [...] All other bindings are initialized during evaluation of the function body.

Here, the specification notes that when FunctionDeclarationInstantiation is called (which is called when you create a new function such as todoApp, the bindings (such as variables or return values) are evaluated during the evaluation of the function body. This implies that the function body is not evaluated when the function is created thus the return value object is not known to contain a reference to todos or visibilityFilter which does not exist yet. This is is also reinforced in Object Initializers:

12.2.6 Object Initializer

NOTE 1 An object initializer is an expression describing the initialization of an Object, written in a form resembling a literal. It is a list of zero or more pairs of property keys and associated values, enclosed in curly brackets. The values need not be literals; they are evaluated each time the object initializer is evaluated.

The last line mentions that the values of an object are not evaluated until the object itself is evaluated.

Thus, since the return value of todoApp is not evaluated until you actually call todoApp, and object values are not evaluated until the return value is evaluated, no ReferenceError is reported because the todos and visibilityFilter references are not evaluated.


On the contrary, your second example is evaluated before todos and visibilityFilter are defined:

export const todoApp = combineReducers({
todos: todos,
visibilityFilter: visibilityFilter,
});

Here, combineReducers is a function call. Because todos and visibilityFilter are function expressions, they are not hoisted, thus the combineReducers call is evaluated before they exist. The object initializer is evaluated because it's an argument to combineReducers, and once the initializer is evaluated, the values are evaluated. The reference to todos and visibilityFilter give a ReferenceError because they do not exist yet.

This also explains the third example as it works because combineReducers is called after todos and visibilityFilter are defined. Your final attempt at putting todosApp between todos and visibilityFilter raises another ReferenceError because todos will be evaluated and exist but visibilityFilter will not because it is not hoisted.

JavaScript function declaration

The first one is simply creating an anonymous function and assigning it to a variable some_func. So using some_func() will call the function.

The second one should be part of an object notation

var obj = {
show:function(value){
// some code here
}
};

So, obj.show() will call the function

In both cases, you are creating an anonymous function. But in the first case, you are simply assigning it to a variable. Whereas in the second case you are assigning it as a member of an object (possibly among many others).

Why do function declarations get hoisted and function expressions don't?

As per MDN,

Conceptually, for example, a strict definition of hoisting suggests that variable and function declarations are physically moved to the top of your code, but this is not in fact what happens. Instead, the variable and function declarations are put into memory during the compile phase, but stay exactly where you typed them in your code.

As you see, in a function expression, actual function is a value assigned to a named variable. So this named variable is hoisted. Even if you have a named function assigned, it still will not be hoisted as it is not a declaration and will be created later.

Sample:

function test() {  console.log(fn, foo);    var fn = function foo() {}}
test();

Javascript parse/evaluation order?

JavaScript, like PHP, tracks top-level function declarations before the code runs.
However, you can bypass the auto-function by using assignments:

var a = function a() { }

Why can I use a function before it's defined in JavaScript?

The function declaration is magic and causes its identifier to be bound before anything in its code-block* is executed.

This differs from an assignment with a function expression, which is evaluated in normal top-down order.

If you changed the example to say:

var internalFoo = function() { return true; };

it would stop working.

The function declaration is syntactically quite separate from the function expression, even though they look almost identical and can be ambiguous in some cases.

This is documented in the ECMAScript standard, section 10.1.3. Unfortunately ECMA-262 is not a very readable document even by standards-standards!

*: the containing function, block, module or script.



Related Topics



Leave a reply



Submit