简体   繁体   中英

Does function declaration order matter in ES6?

I'm learning Redux with Dan Abramov's Redux course videos on http://egghead.io .

I had this code, working fine:

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

export const todos = (state = [], action) => {
    // ...implementation
};

const visibilityFilter = (state = "SHOW_ALL", action) => {
    // ...implementation
}

Then I tried to replace the todoApp reducer function with one generated with the combineReducers helper function:

import { combineReducers } from 'redux'

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

export const todos = (state = [], action) => {
    // ...implementation
};

const visibilityFilter = (state = "SHOW_ALL", action) => {
    // ...implementation
}

But when I try to run the unit tests (which were working fine) I get this error:

ReferenceError: todos is not defined

However, if I cut and copy the todoApp function definition to the bottom of the file (like in the code below), everything works fine again!

import { combineReducers } from 'redux'

export const todos = (state = [], action) => {
    // ...implementation
};

const visibilityFilter = (state = "SHOW_ALL", action) => {
    // ...implementation
}

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

I have also tried to place the todoApp definition between the todos and visibilityFilter functions. In that case, I get the error

ReferenceError: visibilityFilter is not defined

So if I create the todoApp reducer function myself at the top of the file everything works fine, regardless of the function declaration order, but if I use the combineReducers helper function, I need to do it under all the other declarations.

Can somebody explain me why is this happening and what are the good practices to be followed concerning the declaration order?

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.

Yes. If you use the function keyword, the declaration is "hoisted" to the top of the scope, in the same way that the var keyword works.

If you use const or let , then the variable doesn't actually "exist" until the line that it's declared on, and is undefined above that. (The technical term for this is the "Temporal Dead Zone".)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM