Good question! I'll divide this into parts. There's going to be a lot of material to google here, since your question touches multiple deep subjects.
1. Statements have no value
Declarations are statements. They have no value, and thus they cannot be parameters. That this code...
let a = 1
... has no value, means none of these will work:
doStuff(let a = 1)
let b = (let a = 1)
(let a = 1) + 5
The name a, or a + 5, or f(a) are expressions, and unlike statements, expressions have value. But the declaration of a itself does not.
Note that your intuition about this was not absurd: in other languages, let a = 1 is an expression that evaluates to 1. Not in Javascript.
2. Functions are objects
However, the function keyword does have value: the Function object it defines. Unlike variables, which are language constructs for your convenience, Functions are actual objects that exist in the running program. We say that functions are first-class objects.
You can do all of these:
doStuff(function f() {})
let a = function f() {}
let b = (function f() {}) + 5 // the result of this is funny
Back to your examples, then:
callback(
"", // a String object
function b1() { alert("empty") }, // a Function object
function b2() { alert("not empty") } // a Function object
);
Is similar to this:
function b1() { alert("empty") }
function b2() { alert("not empty") }
callback("", b1, b2)
But not quite. Let's talk about scopes.
3. Names are defined within a scope
The scope of a name, such as a variable or function, is the section(s) of code that have that definition available.
For example:
// Top-level scope:
let a = 1
if (a == 1) {
// Inner block scope:
let b = 2
console.log(a, b) // 1, 2
}
console.log(a, b) // 1, undefined
Scopes live inside larger scopes. Inner scopes can access surrounding scopes, (so a and b are visible inside the block) but not the other way round (so b is not visible outside).
When you created your function objects inside the call...
f(function a() { })
... they were trapped inside an inner scope, and cannot be referenced from outside.
4. Assignments are expressions
In your sample code, you noted that declaring a like this worked:
f(a = 5)
This is... unfortunate. A product of Javascript's history, really. In modern code, you should always use let or const to define variables.
So why does it work? Two reasons. First, because it's an assignment, not a declaration. Like so:
let x = 1
f(x = 2)
Assignments are expressions. They evaluate to the assigned value. The value of x = 2 is 2, and x changes as a side-effect.
5. There is a global scope
The second reason is the unfortunate one. When you avoid the let, var or const keywords, you're implicitly using the global scope.
This is the mother of all scopes, and names that live there are accessible from any point in the code. So, if you just do this...
f(a = 5)
... without having declared a anywhere in the current scope, it's implicitly declared in the global scope, and the assignment takes place. Think of it as this (pseudo-code):
global let a
f(a = 5)
That is, of course, not valid Javascript. But you get the point.
function callbackis a function declaration, butfunction b1is not; it is a function expression because it is being used in an expression context not a statement one.console.log(x = 1)logs1.console.log(function() {...})logs the function definition. However,console.log(var x = 1)is a syntax error. While answers will go into detail, defining a function or doingx=1return a value, whereas declaring avardoes not.functionkeyword is that when it's used in a context that expects an expression, it doesn't declare a function, it creates a function expression that produces a function. As a side note, the name of the function has no real purpose here other than helping with debugging and error reporting which is why you usually see anonymous functions used in these cases.