7

The following code always prints the argument passed in to parameter a, regardless of the presence of a variable with the same name.

Presumably because parameter identifiers are bound separately to variables in scope. Where are they positioned? Are they in the lexical environment?

function foo(a, b = () => a) {
  var a = 1
  console.log(b())
}
foo() // undefined
foo(2) // 2

Is it that var declarations end up in the special VariableEnvironment, while parameters are positioned in the LexicalEnvironment? And let and const avoid conflict by making redefinition an early error?

Relevant also:

Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • Another reason not to use `var`... – Patrick Roberts Apr 14 '20 at 13:28
  • 3
    The parameter list has a scope of its own, as I understand it. The closure for that `=>` function has the *parameter* `a` in scope. – Pointy Apr 14 '20 at 13:29
  • Minor edit to illustrate. – Pointy Apr 14 '20 at 13:30
  • 2
    I think [it's this part of the static semantics](https://tc39.es/ecma262/#sec-destructuring-binding-patterns-static-semantics-boundnames). The "bound names" from the list of parameters is added to the set for the next parameter in the formal parameter list. I'm not 100% positive that I'm looking at the right thing however because I'm only halfway into a cup of coffee. – Pointy Apr 14 '20 at 13:38
  • And I haven't figured out (in the spec) how that interplays with the establishment of the function body scope. – Pointy Apr 14 '20 at 13:39
  • 1
    Of course things get wildly more complicated when you consider destructuring syntax in the parameter list, but it's almost like there's a new lexical scope left-to-right for each binding position in the formal parameter list, and the function body scope is *another* lexical layer. – Pointy Apr 14 '20 at 13:42
  • 2
    Sometimes it helps to confirm the expected behavior of various semantics interacting together by just [putting it into babel](https://babeljs.io/repl/#?code_lz=GYVwdgxgLglg9mABMOcAUBDANIgRogXkTQEpCA-RDMgbwChFEA3DAJysMQEYHEIEAznAA2AUwB0wuAHM0uUiToBfOinRkA9BsTgAJqOAwwo3atRoATJu0WgA) and looking at ES5 output which is usually easier to reason about. But to explain my earlier comment, using `let` instead of `var` generates a `SyntaxError`, which I find much preferable to the `var` above implicitly shadowing the parameter list's environment record. – Patrick Roberts Apr 14 '20 at 13:43
  • @Pointy As you probably know, `var` declarations end up in a different logical place to `let` and `const` declarations. `var` declarations end up in a VariableEnvironment, meaning that, if arguments were to be positioned in the LexicalEnvironment with `let`, `const`, they can logically co-exist with `var`s, per the observed behavior. But I cannot (yet) find the part of the spec that defines the position of parameter identifiers (and their associated value). – Ben Aston Apr 14 '20 at 13:54
  • Actually it's probably a little simpler than what I said in my last comment; the parameter "scope" builds incrementally but because a parameter name can't appear twice it's just one scope (maybe). – Pointy Apr 14 '20 at 13:54
  • Well that "Binding" section is linked from the static semantics of the "Function declaration" section; that's what made me think it's relevant. – Pointy Apr 14 '20 at 13:55
  • I mean, is the answer the `arguments` object for normal functions? If so, that would leave fat arrows. – Ben Aston Apr 14 '20 at 14:00
  • 1
    @52d6c6af these semantics don't really correlate one-to-one with observable objects in the language. No, I don't believe it's related to the `arguments` object, since the behavior is identical when changing `foo` to an arrow function. – Patrick Roberts Apr 14 '20 at 14:06
  • @52d6c6af "*`var` declarations end up in a different logical place to `let` and `const` declarations*" - only if they're declared in a block scope. If both declarations happen to be in the same function body scope, they end up in the same place - which is also the reason why you cannot have a `var` and a `let` with the same name. – Bergi Apr 14 '20 at 15:11
  • I interpreted [Table 23](https://tc39.es/ecma262/#table-23) in the spec to indicate that `var` variables were added to the `VariableEnvironment` (which, confusingly, is a LexicalEnironment), and `let` and `const` variables went into the `LexicalEnvironment`. – Ben Aston Apr 14 '20 at 16:14

1 Answers1

2

In the event that any default values are present, a separate environment record is created for parameters.

The semantics of functions declared in this position are such that this environment record defines their local scope. A note in the spec (see clause 28) says:

NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body.

More from the spec:

When an execution context is established for evaluating an ECMAScript function a new function Environment Record is created and bindings for each formal parameter are instantiated in that Environment Record. Each declaration in the function body is also instantiated. If the function's formal parameters do not include any default value initializers then the body declarations are instantiated in the same Environment Record as the parameters. If default value parameter initializers exist, a second Environment Record is created for the body declarations. Formal parameters and functions are initialized as part of FunctionDeclarationInstantiation. All other bindings are initialized during evaluation of the function body.

In the absence of default arguments, therefore, I deduce that one of the pre-existing lexical environments (VariableEnvironment or LexicalEnvironment) is used for parameter bindings. Maybe.

Ben Aston
  • 53,718
  • 65
  • 205
  • 331