The differences between these are subtle, and have to do with scoping.
You can think of function declarations as affecting the scope at compile time, while function expressions gets assigned to a variable at runtime.
Here's some examples of this difference you can try out:
If you paste this into your browsers web console, you'll see that even if f() is called before its definition, the code still works.
Lets try the same with a function expression:
When you run this in the inspector, you'll get: TypeError: undefined is not a function.
In the first case "f" is added to the scope at compile time and the function definition is ignored at runtime. In the second case, the function expression won't get bound to a variable at compile time, and we get an error since we're trying to treat an undefined variable as a function.
Lets try another variation of these examples, to see the difference:
Run this, and you'll see "Second f" in your console, even though the second f() is defined in an unreachable branch of the code.
The same example with a function expression:
This will log "First f" to your console when run, since the second function expression is never reached.
One place where this distinction gets way more obvious is if we try to define functions within a branch like this:
Here function declarations won't work. Even when a console is present, greet("Someone") will still give you an alert. Function expressions will work as expected:
In this case modern browsers will use the console for the greeting.
The Binding Identifier
The function expressions with binding identifier ads yet another twist to all of this.
In most ways it acts just like a function expression, but there's one subtle difference for recursive functions: the binding identifier can be used to reference the function from within the function body, but won't be declared in the scope the function is declared in:
If you try this in the inspector, you'll first see a countdown from 2 to 0 in the console when countdown(2) is called, and then "ReferenceError: count is not defined".
The Future: MOAR WAYS!
Now, it might be tempting to think that this is just a bit of syntactic sugar for using function expressions, but no, no, no...
ArrowFunctions comes with their own subtle variation when it comes to scope. This time related to how this is treated:
You can try this in the web console of Firefox Nightly where arrow functions are now implemented. The result might surprise you: you'll see first "undefined" and then "42" in the console log.
This will output "42" and "huh" in the console of the current Firefox Nightly.
The funcitions "a" and "b" have the same function bodies and both "typeof a" and "typeof b" will return "function", but yet they behave quite differently when we use apply. The function defined as an arrow function simply ignores the scope argument and sticks to the lexical scope it was defined in.
Functions declared with the arrow syntax also can't be instantiated with "new" and don't have access to the special "arguments" object within their function body.
Wait! There's more!
So, with all these ways of defining functions, you might think we were done, but no, no, no...
The way arrow functions behave with regard to this means they're not suited to define methods on an object (where you would want this to point to the object).
To handle this ECMAScript 6 introduces yet another way of declaring a function: MethodDefinitions.
This is still not implemented in FireFox, so I haven't been able to play around with it, but here's how the syntax would look:
This ought to log "42" to your console if you could somehow run it.
What to do?
There's obviously no right answer to the question, but personally I tend to stick with function expressions.