Interesting question. Wording can have soft boundaries, which is partly what causes this confusion.
First, some rough definitions:
- Expression: I think this is best thought of with an example.
2 * 2
is an expression, because you can continue doing other operations on it, like 2 * 2 - 3
. if (...)
on the other hand is not an expression in javascript, it does not resolve into a value in which you can do further operations on. It's invalid to do if (...) {...} / 2
. This "definition" has some holes, but should be good enough for the purposes of this answer.
- Declaration: A declaration is just declaring that you're going to use a certain variable name. So, in your example with
const foo = 5
, It's the const foo
part that actually declares, the = 5
is just initializing the declaration.
With that in mind, lets consider two examples of functions, and see how this terminology plays into these examples:
const g = function() {}
There is a declaration going on here, the const g
. We also see that there's a function being created and assigned to g. We're doing something with the result of function() {}
(we're assigning it to something), which means javascript will interpret this as a function expression.
function f() {}
We're not using this function as an expression (we're not doing any additional operations with it after we create it), so javascript will actually treat this different from our first example. It's going to declare f in the local namespace, and it's going to apply function hoisting. If you simply added a +
before function f() {}
, then you would cause javascript to interpret this as an expression instead, and these features will be disabled. You can try it in the dev tools - put in +function f(){}
or const g = function f(){}
, and f will remain undefined.
The fact that javascript treats the function keyword differently in different contexts is at the heart of this language choice. You can almost think of the "function" keyword as having two different meaning, and the chosen meaning depends on the context. Any time the function is used in an expression context or when it's unnamed, then it takes on the "expression" form, and we call it a "function expression". Otherwise, it takes on the alternative form that contains some extra features, like declaring the function name as a local variable. This is probably why we've adopted to call this alternative form a function declaration. However, like the OP rightly pointed out, even with const f = function() {}
, a declaration is still happening, it's just not happening by the function
keyword.
Footnote: With examples like +function f() {}
, f won't be defined in the same scope as g, but it does get defined inside the body of f. In other words, any named function expression declares its name within its own body.