2

In the below code snippet, i is declared after function f, so how can function f access i's value?

let f;

if (true) {
  f = () => {
    console.log(i)
  }

  let i = 1;
}

f();
connexo
  • 53,704
  • 14
  • 91
  • 128
Neon
  • 490
  • 2
  • 9
  • 1
    because you're calling f() after declaring i. Call f() before i's declaration and you'll get the error you're expecting. For example, try `f = () => { console.log(i) } f(); let i = 1;` and you'll get error – Sagar V Nov 06 '19 at 16:53
  • @SagarV That is not the reason. You just suggested a possible error OP *could have made* (but didn't). And even then it would'nt error, it would just log `undefined`. – connexo Nov 06 '19 at 16:56
  • @Neon In Javascript Variables are [Hoisted](https://www.w3schools.com/js/js_hoisting.asp), what that means ? well In JavaScript, a variable can be declared after it has been used. In other words; a variable can be used before it has been declared. – stan chacon Nov 06 '19 at 16:58
  • Does this answer your question? ['Hoisted' JavaScript Variables](https://stackoverflow.com/questions/29667199/hoisted-javascript-variables) – Dan O Nov 06 '19 at 17:02
  • MDN has a good explanation https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let – epascarello Nov 06 '19 at 17:05

4 Answers4

4

i is declared after function f

Ehm, yes, but in the same scope (block).

i is declared in the same block as the value of f (an anonymous function that uses i), so it is in scope:

f = () => {
    console.log(i)
}

// same scope as the definition of the anonymous function:

let i = 1;

Now at the time of declaration, the anonymous function does not need to know what i is, and if you never declared it, it would just resolve to undefined when you do f().

At the time of execution, i is already declared and assigned a value. The anonymous function "recalls" this variable because it was declared in it's scope. This is called a closure.

connexo
  • 53,704
  • 14
  • 91
  • 128
  • But let variables aren't hoisted from what I learnt. Isn't that right? – Neon Nov 06 '19 at 16:57
  • How come the value is accessible inside the function f if the assignment of i is not hoisted? – Neon Nov 06 '19 at 17:05
  • @Neon Good observation. Corrected my answer accordingly. – connexo Nov 06 '19 at 17:09
  • @Neon let and const are hoisted too. With `var`, they are initialized to undefined but with let and const they are only declared and initialization happens later. [Are variables declared with let or const not hoisted in ES6?](https://stackoverflow.com/questions/31219420) – adiga Nov 06 '19 at 17:15
  • @adiga I don't think that's true. How do you explain the error that occurs in my answer's example code if `let i` is hoisted? – apsillers Nov 06 '19 at 17:17
  • @apsillers It doesn't throw *"i is not defined"* but it throws *"Cannot access 'i' before initialization"*. With `let` and `const`, only the declaration is hoisted. The variable cannot be accessed before the `let i` line runs but the identifier is created. The variables are in a [Temporal dead zone](https://stackoverflow.com/questions/33198849). With `var`, the declaration is hoisted and it is initialized to undefined at the top of the scope. With `const` and let, the initialization happens when the `let i = ''` line runs. It is explained in detail in the linked question – adiga Nov 06 '19 at 17:25
  • @adiga Having read your linked Q&A, I think I misunderstand what "hoisted" means. It is clear to me, experimentally, that the variable declared by `let` may not be used or accessed until after the declaration occurs (vs. `var` where the declared variable is accessible from the top of scope), but apparently this does not disqualify the declaration from being called "hoisted" which I don't yet understand. – apsillers Nov 06 '19 at 17:25
  • @apsillers These two examples are pretty good [What causes the different behaviors between “var” and “let” when assign them a returned value of a function which throws an error](https://stackoverflow.com/questions/54231651) and [Is there any difference between declared and defined variable](https://stackoverflow.com/questions/54979906) – adiga Nov 06 '19 at 17:30
  • 1
    @adiga Thanks, that question about temporal dead zones is helpful: `let` declarations are hoisted insofar as they cause a scope-wide block on accessing outer-scope variabless of the same name. They create the variable from the top of the scope, but prior to the declaration, the only utility the variable has is to shadow other variables. Thanks for pointing out this clarification! – apsillers Nov 06 '19 at 17:33
4

let keyword scope.

First of the let keyword scope for block of code in your program 'i' is inside of if block so 'i' value scope is inside of if block.

so,if you create 'n' number of function inside of if block it'll access the 'i' value.

please check my example remove the comment in f1() inside console.log(j) and put debugger you'll understand clear.

Here, you'll get error because 'j' is only access for f() means 'j' scope only inside of f(). If you use inside of f() it'll access. Check f2() it's able to access 'j' value.

 <script>
debugger
let f,f1,f2;


if (true) {
  f = () => {
    console.log(i)
    let j=11;
    f2=()=>{
    console.log(j);
    }
  }

  f1=()=>{
  console.log(i);
  //console.log(j);
  }
  let i = 1;
}

f();
f1();
f2();
</script>
Prabhat
  • 772
  • 1
  • 8
  • 22
2

You are correct that the variables declared by lexical let declarations may not be accessed without error until after the declaration lexically occurs. (This is in contrast to var declarations, which make their declared variable accessible and referencible starting from the top of their containing scope, regardless of where the declaration occurs within that scope.)

The variables referenced by name in f are not accessed until f is actually called. The JavaScript engine does not ask the question "In what scope is the variable i and what is its value?" until f is executed. At the chronological time f actually is executed, the surrounding block scope has had the variable i made accessible within it, so f can use that variable to resolve the identifier i.

When you refer to i at execution time, the JavaScript engine looks up the scope chain to find the nearest declared variable named i. The declaration let i is an instruction to the JavaScript engine to make available a variable named i to the current block scope. The fact that the surrounding scope has no accessibl i at the time f is defined is immaterial; what matters is that the scope does have a variable i by the time f is executed.

Consider this example:

var f;

if(true) {
    f = ()=>{ console.log(i) }

    try { f(); } catch(e) { console.log(e); }
    let i = 1;
    try { f(); } catch(e) { console.log(e);}
}

The first call produces an error, because during the execution of f, no i has yet been made accessible, while the second execution, after the let i statement, runs without error.

apsillers
  • 112,806
  • 17
  • 235
  • 239
1

As Javascript is an interpreted language, it will execute statements one at a time, line by line, without looking at the entire code first (unlike compiled languages). What this means is that your function will not throw an error at the line console.log(i); before runtime.

At runtime, you are defining and initializing i before calling the function f, so by the time f is called, the value of i is already known.

Regarding scopes, i and f are declared in the same block (not inside a function or anything), therefore f can access i fine. No scope issues there.

Anis R.
  • 6,656
  • 2
  • 15
  • 37
  • It is the scope that is being questioned. – Kalesh Kaladharan Nov 06 '19 at 17:59
  • Yeah it's the term `block` that confuses most users like me coming from a Java or C++ background. In java and other similar languages, a block used to be statements within curly braces and the variables declared within those braces are available only within those braces. Hence the confusion. Building things using ES6 OOP approach cn help sove this 9/10 times :) – Kalesh Kaladharan Nov 06 '19 at 18:28