I'm reading the book JavaScript: The Good Parts.
On page 113 it recommends function expressions instead of function statements, because statements are subject to hoisting:
The statement:
function foo( ) {}
means about the same thing as:
var foo = function foo( ) {};
Throughout this book, I have been using the second form because it
makes it clear thatfoo
is a variable containing a function value.
Further JSHint warns against function statements defined in a block, e.g. (my example):
if (foo) {
function baz() {}
}
… e.g. because support for this isn't consistent between javascript engines; it produces the following error message:
Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.
A recommended work-around is a function expression, e.g. (my example):
if (foo) {
baz = function() {}
}
My question is, where do I declare the baz
variable?
The end of page 36 (the Scope section of the Functions chapter) says,
In many modern languages, it is recommended that variables be declared as late as
possible, at the first point of use. That turns out to be bad advice for JavaScript
because it lacks block scope. So instead, it is best to declare all of the variables used
in a function at the top of the function body.
Does that mean that it's best to declare the baz variable at the top of the function?
For example, like this:
function(foo) {
// declare variables
var baz;
// implementation
if (foo) {
baz = function() {}
}
}
Is this a best practice, or am I misunderstanding something, reading too much into it? I fear it makes it harder to maintain: because it separates the declaration of baz (i.e. var baz;
) from its initialization. If I rename or delete one but not the other then suddenly the variable would exist at global scope.
Isn't it safer to code as follows, where var
is used where the variable is first used?
function(foo) {
// implementation
if (foo) {
var baz = function() {}
}
}
I understand that the latter is functionality equivalent to the former. Is the latter contrary to a best-practice coding-style for Javascript?
Best Answer
So the generic answer to this question is "wherever you want", but of course that's not helpful and has pitfalls, so I'm going to tell you where I recommend putting it, and attempt to defend my logic as best as possible.
No matter where you declare a function* or variable** in JavaScript, the function or variable will be hoisted to the top of its containing scope***. If there is no containing scope, it will be implicitly added to the global namespace (in browsers this is
window
, in NodeJS this isglobal
).When a variable or function is hoisted, it means that the JavaScript interpreter will pretend as though your variable declaration were written as the first line of the containing scope.
In practice what this means is that when you write:
It's actually evaluated as:
* I'm referring to function declaration here. I.E.
function name(...) {...}
** I'm referring to variable declaration with
var
here. I.E.var name
*** Containing scope is typically the wrapping function, but sometimes for various reasons there is no wrapping function.
Because of hoisting this classic best practice becomes susceptible to simple mistakes.
Consider the case where you write some code...
Then you come back later, and need to add some new functionality, so you add:
Now, if you combine the two it's easy to see in this contrived example that sloppiness has led to accidentally redeclaring and overriding the
i
variable.This might seem silly, but it happens all the time. It's quite easy if you don't actively take steps to avoid it. This sort of an issue isn't a problem in languages with block scope, because
i
would be contained in eachfor
loop's scope, and to usedoSomethingWith
, we would have had to expose the value ofi
to the parent scope.With that all said, your question is really one of best practices. This is inherently subjective and will lead to disagreement. I'm going to post my opinion and I will attempt to defend it, but it should be treated as an opinion and not dogma.
In my opinion, functions should have the following flow:
(1) Directives are things like
"use strict"
. They come first by definition.(2) Variable declaration using
var
comes second. They could come after function declaration, or even in between, but I've found it makes them harder to find. I also recommend using exactly onevar
statement with each var placed on separate lines, organized alphabetically. This forces variables to be clustered together, and alphabetical ordering prevents accidental duplication.(3) Function declaration comes next because functions are hoisted just like variables. Seeing what local APIs are available within a given scope is something I find useful.
(4) Variable instantiation comes after function declaration, and in my opinion should be separate from declaration. Yes there are circumstances where it's easier to just put them on the same line, but as a general rule, you can't go wrong with keeping them separate.
The reasoning for this one is two-fold.
First, the declaration will be interpreted to happen before the assignment, so writing code that reads the way the interpreter is going to interpret it will help with understanding.
Second, if the assignment is merged with the declaration, order suddenly starts to matter, and you lose the ability to consistently alphabetize your variables.
As an example:
If you separate the declaration from assignment, you can get the benefits of both:
(5) Once you're done with all the setup for a function, you'll need to write the rest of the code. This step is the "rest of the code" step.
If you've been paying attention to ECMAScript2015, then you should know that there are two new ways to declare variables:
let
andconst
.These new variable declarations use block scope. If you're assigning a variable that will never be overwritten, use
const
, otherwise uselet
.Going back to:
let
allows you to do this. If you can uselet
, stop usingvar
; uselet
andconst
where the variables are first used; combine declaration with assignment:ES5 way:
ES2015 way: