10

The simple version of this question is: Why is there an undefined error in the 3rd example from the snippet below?

Intuition why it should work

The default value should, it seems, be taken from the "outer" a variable, ie, the one whose value is 1. The first test shows that "shadowing" works with lexical scope: a inside the function refers only to a inside the function, and is unaware of the outer a.

Given that, I see no reason why the 2nd and 3rd tests are different. It is simply an arbitrary coincidence that in the 3rd test, I happen to be setting the default value to a variable in the enclosing scope that has the same name as the function's parameter.

var a = 1;
var b = 100;



function defaultParamTest1(a) {
  console.log(a + 1);
}

function defaultParamTest2(a = b) {
  console.log(a + 1);
}

function defaultParamTest3(a = a) {
  console.log(a + 1);
}


defaultParamTest1(); // WORKS! => NaN, since function a "shadows" outer a.
defaultParamTest2(); // WORKS! => 101, as expected
defaultParamTest3(); // ERROR! => "Uncaught ReferenceError: a is not defined"
Jonah
  • 15,806
  • 22
  • 87
  • 161
  • 1
    I think I know the answer, but I'll check and not just speculate – Pointy Jun 05 '16 at 22:05
  • Even if it did work, it would be quite confusing for someone reading the code sometime down the line imo – JCOC611 Jun 05 '16 at 22:06
  • 1
    because of the scoping in JS. In the definition of a function the arguments belong to the scope of the function and the names within the parens are evaluated first by their existence in the scope of the function. In the third snippet `a` is an existing parameter in the function's scope and it is undefined. – Redu Jun 05 '16 at 22:07
  • 1
    @Redu Mostly true, but be aware that while the argument values themselves are scoped to the function body, the evaluation of function default arguments actually happens their own third scope, which is neither the function body, nor the outer scope. – loganfsmyth Jun 05 '16 at 22:21
  • why cant you just remove the `a = a`? if the `a` is in the scope of the function, why redefine the variable with itself when you can just use the variable without redefining? – ZomoXYZ Jun 05 '16 at 23:08
  • @Jaketr00, that wouldn't be the same. You would no longer be able to pass in a new value to override the default. the point here is that you want a normal function that takes a parameter, and if that parameter is not given, it should default to the value of the parameter name `a` in the enclosing scope. – Jonah Jun 05 '16 at 23:20
  • Related, if not duplicate: [Scope of Default function parameters in javascript](https://stackoverflow.com/q/44896829/1048572) – Bergi May 23 '22 at 15:45
  • if you googled here because you got `Cannot access 'a' before initialization` when default argument name is the same as variable, argument is named and you don't want to rename either of them, you can replace `{a = a}` with `{a: _a = a}` – quant2016 Sep 02 '23 at 09:34

1 Answers1

9

OK, I haven't read the spec itself because the last Krell brain boost I had is wearing off, but I think the issue is that the right-hand side expressions for default parameters include the set of parameters in their scope. Thus, the a you reference on the right-hand side of the = is the a on the left-hand side, not the a in the enclosing context. (The context is available of course, as you note, but parameter names shadow that context.)

The evaluation of the right-hand side default expressions is an invocation time thing in ES2015.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 3
    I did read the spec, you can find this described in [§9.2.12 FunctionDeclarationInstantiation](http://www.ecma-international.org/ecma-262/6.0/#sec-functiondeclarationinstantiation) (no recommended reading). The nice thing is that you can do `function (a, b = a) { …` or even create closures that refer to parameters in the default initialisers. Notice that `function (a = b, b) { …` is a TDZ-ReferenceError. – Bergi Jun 05 '16 at 22:26