2

1

function test(params) {
    console.log(color);
}

let color = "red";

test();

2

function test(params) {
  console.log(color);
}

test();

let color = "red";

1 function seems to be able to 'see'/access color because the line which invokes the function can see color. When the line that invokes the function is above color (like in 2) the only way color is seen is if it is 'above' or inside the function some way.

Is it fair for me to deduce this rule:

FunctionA can access a (non-global) variable that exists outside functionA when it is seen by the line that invokes functionA?


Please don't comment linking to a big document about closures. I'm talking about a specific situation.

tonitone120
  • 1,920
  • 3
  • 8
  • 25
  • Nope. This is how "scope" works. Inner scopes can access variables declared along parent scopes, but not sibling scopes or children scopes. – Daniel Cheung Sep 21 '20 at 17:11
  • 1
    I think [Are variables declared with let or const hoisted?](https://stackoverflow.com/questions/31219420/are-variables-declared-with-let-or-const-hoisted) helps explain what is going on here. `test` cannot access the variables from the context of the caller, it can only access variables that are declared or going to be declared in the same scope as the function definition. – 3limin4t0r Sep 21 '20 at 17:19
  • @3limin4t0r Are we saying that as soon as a variable is 'read' it's hoisted to the top of its scope? – tonitone120 Sep 21 '20 at 17:42
  • @tonitone120 I think this article will explains it better than I can. [MDN Hoisting](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting) In short if it is declared somewhere within the same scope you can reference it, however it may be `undefined` when defining it with `var` or result in an error when defining it with `let` or `const`. – 3limin4t0r Sep 21 '20 at 20:14

1 Answers1

1

FunctionA can only access a (non-global) variable that exists outside functionA when it is seen by the line that invokes functionA?

No. Counterexample:

const getTest = () => {
  function test(params) {
      console.log(color);
  }

  let color = "red";
  return test;
};

const test = getTest();
// Here, `color` cannot be seen
// but the function still runs fine
test();

Whether a variable can be seen at a particular point in code depends (for most situations worth caring about) the lexical position of the variable's definition in relation to the point where it's being used.

A variable can be seen if it's initialized (eg with let or const) in the same block, or in an ancestor block.

A variable cannot be seen if it's initialized (eg with let or const) in a block which is not the same or an ancestor block.

The call chain does not affect scope.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • _"Please don't comment linking to a big document about closures."_ - A valid example but OP ruled them (closures) out, imho (even though this isn't a _"big document"_). – Andreas Sep 21 '20 at 17:13
  • @CertainPerformance so `color` is seen by `test` (in `getTest`) because it's in the same block (`getTest`)? That can't be right – tonitone120 Sep 21 '20 at 17:22
  • @tonitone120 Yes. Variable declarations are hoisted to the top of the block. However with `let` and `const` the definitions are not. So when you try to run `test()` before `let color = "red"` it will result in an error. – 3limin4t0r Sep 21 '20 at 17:28
  • @3limin4t0r I don't know what you mean by 'However with `let` and `const` the definition is not.' – tonitone120 Sep 21 '20 at 17:36
  • 1
    Whether an identifier can be seen at a certain point in the code only depends on whether the identifier is declared in the same or an outer block. It may still be in the Temporal Dead Zone (in which case it'll throw an error: not initialized yet), but it'll still be lexically visible (if it wasn't, it'd throw a *different* error: not defined) – CertainPerformance Sep 21 '20 at 17:42
  • 1
    If an identifier is initialized at any point inside a block, that identifier can be referenced anywhere inside that block, including inside descendant blocks, as long as the `const` or `let` initialization line has run first. – CertainPerformance Sep 21 '20 at 17:45
  • @CertainPerformance Right, so in my code, if I switch `color` with `test()`, `test` sees `color` but the error is `color` hasn't been initialised (which I thought just meant 'assigned a value'. The reason why this definition must be wrong is because you can successfully reference a variable that hasn't been assigned a value - the variable just returns `undefined`). – tonitone120 Sep 21 '20 at 18:00
  • 1
    @tonitone120 That is only true when using `var`, which declares the variable and initialize it to `undefined` (both declaration and initialization are hoisted). Using `let` or `const` will still let you reference the variable but you get an error that it isn't initialized yet (only declaration is hoisted). For example `console.log(foo); var foo;` will log `undefined` while `console.log(bar); let bar;` will result in "Uncaught ReferenceError: can't access lexical declaration 'baz' before initialization". – 3limin4t0r Sep 21 '20 at 20:09