9

It might be a dumb question. I googled it but can't find the answer. Variable declaration is not allowed as parameter of a function as below.

function t(a) {
    alert(a);
}

t(var x = 1); // Uncaught SyntaxError: Unexpected token var
t(let x = 1); // Uncaught SyntaxError: missing ) after argument list
t(x = 1); // working fine and later I am able to access x also
console.log(x); // printing 1

But function declaration is being allowed as a parameter of a function as below.

function callback(str, f1, f2) {
    if(str == "")
        f1();
    else
        f2();
};

callback("", function b1() { alert("empty") }, function b2() { alert("not empty") }); // working fine

b1(); // Throwing error Uncaught ReferenceError: b1 is not defined

Can anyone please help me to understand

  • Why variable declaration is not allowed as a parameter of a function but function declaration is allowed as parameter?
  • Why we can't access the function which is declared as a function parameter outside of the function call?
4
  • 1
    function callback is a function declaration, but function b1 is not; it is a function expression because it is being used in an expression context not a statement one. Commented Jan 4, 2019 at 2:25
  • 1
    console.log(x = 1) logs 1. 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 doing x=1 return a value, whereas declaring a var does not. Commented Jan 4, 2019 at 2:25
  • 1
    Simply put, declarations are never legal as parameters. Neither variable declarations nor function declarations. The thing with the function keyword 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. Commented Jan 4, 2019 at 2:41
  • @Paul a function declaration and a function expression are frustratingly similar. Initially I thought you were wrong, but indeed the context in which you call it is important. I tried to go a bit deeper in my answer Commented Jun 28, 2022 at 18:02

2 Answers 2

16

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.

Sign up to request clarification or add additional context in comments.

Comments

0

The shortest way I can think of explaining the difference is if we convert the named function expression argument (the context where it is called is very important) in your example:

callback("", function b1() { console.log("empty"); }, ...)

to a variable declaration argument:

callback("", var b1 = function() { console.log("empty"); }, ...)

This gives an error:

function callback(str, f1, f2) {
    if(str == "")
        f1();
    else
        f2();
};

callback(
  "", 
  var b1 = function () { console.log("empty") }, 
  var b2 = function () { console.log("not empty") }
); // Error: expected expression, got keyword 'var' 

This works fine:

function callback(str, f1, f2) {
    if(str == "")
        f1();
    else
        f2();
};

callback(
  "", 
  function b1() { console.log("empty") }, 
  function b2() { console.log("not empty") }
); // logs 'empty'

Function declarations vs function expressions

They're almost the same, the MDN reference hierarchy is quite informative for the difference:

Operators/function (expression):

function [name]([param1[, param2[, ..., paramN]]]) {
  [statements]
}

Statements/function (definition):

function name([param1[, param2,[..., paramN]]]) {
   [statements]
}

See how an expression is listed under 'operators', whereas declaration is listed under 'statements'.

Note: I made slight adjustments to the MDN syntax to show how similar they are - they both have numbers after their parameters and for both declaration and expression the statements inside are optional.

What is important is where you call them:

function b1() {console.log('empty');} // declaration

callback(
  ""
  function b1() {console.log('empty');} // expression
)

Similarly a ternary (Operators/Conditional_Operator) str == "" ? f1 : f2 is an operator/expression where as if (Statements/if...else) if (str == "") { return f1 } else { return f2 } is a statement/declaration.

function callCallback(cb) {
  cb()
}

function callback(str, f1, f2) {
  callCallback(str == "" ? f1 : f2)
}

callback(
  "", 
  function b1() { console.log("empty") }, 
  function b2() { console.log("not empty") }
); // logs 'empty'

function callCallback(cb) {
  cb()
}

function callback(str, f1, f2) {
  callCallback(if (str == "") { return f1 } else { return f2 })
}

callback(
  "", 
  function b1() { console.log("empty") }, 
  function b2() { console.log("not empty") }
); // Error: expected expression, got keyword 'if'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.