javascript-tutorial / en.javascript.info

Modern JavaScript Tutorial
https://javascript.info
Other
23.51k stars 3.86k forks source link

Chapter: Variable scope, closure, modification suggestion #2068

Closed ajfg93 closed 4 years ago

ajfg93 commented 4 years ago

There is an "Army of functions" exercise in this chapter. And I suggest some modifications for the explanation/solution.

If I understand correctly, using let doesn't create a new Lexical Environment and it is the {} block clause creates a new Lexical Environment each time and let just creates a local variable inside the Lexical Environment, while var hoists the variable into the function level scope when creating a variable. So, whether using let or not, there is a new Lexical Environment created each run in for(){} or while{}.

But for the first time I read the solution/explanation, I thought it was using let that creates a new Lexical Environment. Kinda misleading until I read some thing on MDN about let.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

However, it's important to point out that a block nested inside a case clause will create a new block scoped lexical environment, which will not produce the redeclaration errors shown above.

The quotes above did not direclty clear my doubt but made me thought twice. And then I got my coherent conclusion.


Parts that made me mostly confused for the first time:

Now it works correctly, because every time the code block in for (let i=0...) {...} is executed, a new Lexical Environment is created for it, with the corresponding variable i.

The while loop, just like for, makes a new Lexical Environment for each run. So here we make sure that it gets the right value for a shooter.

And becasue you are adding a new let or replacing var with let in each new code snippet. So I was more likey to believe that it was let that somehow creates a new Lexical Environment.

To sum up, I suggest making some modification here and let the readers know let does not create a new Lexical Environment, while {} does.

Anyway, it's just my opinion. (Or my conclusion could totally be wrong. I write C++ most of my times.)

--

In the end, I would like to thank you so much for the tutorial. It's so far the best I could find in the Net. So much better than the classic book I've read. I am going to buy a full tutorial.

MuhammedZakir commented 4 years ago

If anything confuses you, do tell! I will try my best! :-)


If I understand correctly, using let doesn't create a new Lexical Environment and it is the {} block clause creates a new Lexical Environment each time and let just creates a local variable inside the Lexical Environment, while var hoists the variable into the function level scope when creating a variable. So, whether usinglet or not, there is a new Lexical Environment created each run in for(){} or while{}

That's correct!

But for the first time I read the solution/explanation, I thought it was using let that creates a new Lexical Environment.

You should read the solution again. It explains correctly! Below is the code snippet from task - https://javascript.info/task/make-army.

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function() { // shooter function
      alert( i ); // should show its number
    };
    shooters.push(shooter);
    i++;
  }

  return shooters;
}

let army = makeArmy();

army[0](); // the shooter number 0 shows 10
army[5](); // and number 5 also outputs 10...
// ... all shooters show 10 instead of their 0, 1, 2, 3...

And becasue you are adding a new let or replacing var with let in each new code snippet. [...]

As you can see above, variable i is declared using let, not var. Another thing to note is that, we are actually pushing a function to shooters array, not a value. What that means is, whenever a shooter function is called, value of i is taken from its lexical environment (see below).

From solution:

Now why all such functions show the same? That’s because there’s no local variablei inside shooter functions. When such a function is called, it takes i from its outer lexical environment.

--snip--

…We can see that it lives in the lexical environment associated with the current makeArmy() run. [...]

Here it explains,

  1. variable i is declared inside makeArmy function's main body and thus it's scope is inside makeArmy function.
  2. there is no variable i inside shooter function and hence,
  3. value of variable i in shooter function is taken from its outer lexical environment.

tl;dr - whenever a shooter function is called, value of i is taken from its outer lexical environment.

From solution:

[...] But when army[5]() is called, makeArmy has already finished its job, and i has the last value: 10 (the end of while).

As a result, all shooter functions get from the outer lexical environment the same, last valuei=10.

This explains that

  1. at the end of while loop in makeArmy function,i has the value 10 and hence,

  2. at the end of makeArmy function, i has value 10.

  3. makeArmy function's lexical environment has a variable named i with value 10

  4. whenever any of the functions in army array is called, value of i is taken from shooter function's outer lexical environment. It looks something like this

    • call army[5]()
    • call alert(i) ->
      • looks in the lexical environment -> found no variable named i ->
        • looks in while loop's lexical environment -> found no variable named i ->
          • looks in makeArmy function's lexical environment -> found variable named i with value 10
      • show an alert with 10
    • exit function

Important: This is just for the sake of explaining. This is NOT how it is actually done!


While loop solution:

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let j = i;
    let shooter = function() { // shooter function
      alert( j ); // should show its number
    };
    shooters.push(shooter);
    i++;
  }

  return shooters;
}

let army = makeArmy();

Here, variable i's value is assigned to j and show alert with j.

What happens in the first case is that there is no variable i in while loop. So on each iteration, while loop create a new lexical environment and put something like this --

there is nothing here. you might find what you are looking for in makeArmy()'s lexical environment

That's why, all footer functions show an alert with 10.

In second case, while loop's lexical environment is --

j=<current_value>. if that's not what you're looking for, you might find it in makeArmy()'s lexical environment

See https://javascript.info/task/make-army/lexenv-makearmy.svg. So it shows alert with values we expect!

Important: This is just for the sake of explaining. This is NOT how it is actually done!

MuhammedZakir commented 4 years ago

Parts that made me mostly confused for the first time:

Now it works correctly, because every time the code block in for (let i=0...) {...} is executed, a new Lexical Environment is created for it, with the corresponding variable i.

The while loop, just like for, makes a new Lexical Environment for each run. So here we make sure that it gets the right value for a shooter.

You are right! This part can be interpreted in a different way. Some restructuring is needed. Maybe swapping for solution and it's description with while solution? After all, while is used in task.


@iliakan : I think an explanation of for inline variable declaration's binding when using var and let is necessary. https://2ality.com/2015/02/es6-scoping.html#let-in-loop-heads

iliakan commented 4 years ago

The information is there, but let's try to improve the text =)

Could you suggest a PR please?

MuhammedZakir commented 4 years ago

I'll try!

P.S. Are you okay swapping for with while?

ajfg93 commented 4 years ago

@MuhammedZakir Go try mate. You seem to know Javascript better than me. I suggest not adding too much extra words. Just try to rephrase some of the text and clear the possible misleading part.

MuhammedZakir commented 4 years ago

@ajfg93 : What do you think?

https://github.com/MuhammedZakir/en.javascript.info/blob/master/1-js/06-advanced-functions/03-closure/10-make-army/solution.md

Edit: This maybe interesting to you -- https://www.ecma-international.org/ecma-262/6.0/#sec-blockdeclarationinstantiation.

xxleyi commented 4 years ago

@MuhammedZakir Maybe you don't want to introduce for loop about this solution. Here is a good video: JavaScript for-loops are… complicated - HTTP203 - YouTube

All the point is let in the initializer of for loop does have its own special and complicated runtime semantics.

Here is spec: https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation

MuhammedZakir commented 4 years ago

You should know this chapter is in advanced section and almost all chapters use let for variable declaration. So I guess it's fine to put for solution here. However, I think we should have a section about using var vs let as for inline-variable declaration. @iliakan: if there is an explanation about it, where exactly is it? If it is explained before this chapter, then it's even more okay! If it is after this chapter, then either put a note or remove it(?). If there is no such explanation anywhere, we can decide whether to remove it or not after deciding where to put that explanation.

@xxleyi: Any input?

[...] Here is a good video: JavaScript for-loops are… complicated - HTTP203 - YouTube

Thanks for sharing this! 👍 FYI, I just tested the last code in this video in Chrome, Firefox & Safari and found out that each of them handle it differently! 😪

MuhammedZakir commented 4 years ago

@iliakan : I couldn't extract strings from the image I created. How should I fix it? I am not familiar with SVG.

https://github.com/MuhammedZakir/en.javascript.info/blob/master/1-js/06-advanced-functions/03-closure/10-make-army/task-while-lexenv-makearmy.svg

iliakan commented 4 years ago

Have you created a new image from scratch?

MuhammedZakir commented 4 years ago

Have you created a new image from scratch?

Yes!

xxleyi commented 4 years ago

@MuhammedZakir I tested too, Safari 12 is 1, Firefox 68 and Chrome 84 is 0. Maybe it's a bug in Safari 12 ?

And I found such explanation in https://javascript.info/closure

Now it works correctly, because every time the code block in for (let i=0...) {...} is executed, a new Lexical Environment is created for it, with the corresponding variable i.

This explanation is good for practice, but is not very clear if we think more, the description the corresponding variable i is vague.

Maybe this can be replaced with a more accurate explanation.

MuhammedZakir commented 4 years ago

@MuhammedZakir I tested too, Safari 12 is 1, Firefox 68 and Chrome 84 is 0. Maybe it's a bug in Safari 12 ?

I guess so!

And I found such explanation in https://javascript.info/closure

Now it works correctly, because every time the code block in for (let i=0...) {...} is executed, a new Lexical Environment is created for it, with the corresponding variable i.

This explanation is good for practice, but is not very clear if we think more, the description the corresponding variable i is vague.

Maybe this can be replaced with a more accurate explanation.

  1. "Now it works correctly, because every time the code block in for (let i=0...) {...} is executed, a new Lexical Environment is created for it, with variable i, along with it's current value."
  2. "Now it works correctly, because on each iteration, a new lexical environment is created for it, with variable i, along with it's current value."
  3. Or "with variable i and its value at the time of iteration."

Have you created a new image from scratch?

@iliakan: FYI, I could't find a free SVG editor for Mac. I found a web app, but it wasn't easy. So I created it using 'drawio'. It's not an SVG editor, but can export to SVG. I will try to figure it out if you tell me what to do to make it right.

iliakan commented 4 years ago

@MuhammedZakir maybe you just tell me what to change?

So that we won't spend time exploring this without a good reason =)

MuhammedZakir commented 4 years ago

If you are talking about SVG, I honestly don't know! That was my first SVG! So I don't know what to do. There isn't any need for translation for the image I created for this chapter, but it would be helpful to know how to make my images compatible with your string extractor, in case, if I had to one that needs translation.

Also, if you are okay with the changes I made, shall I create a PR?

iliakan commented 4 years ago

For translation purposes, you shouldn't create an SVG of your own.

Do you want to add an additional image or modify an existing one? Besides translation.

MuhammedZakir commented 4 years ago

For translation purposes, you shouldn't create an SVG of your own.

Ok.

Do you want to add an additional image or modify an existing one? Besides translation.

For image, none! For solution, "the corresponding variable i" part is the only thing left.

iliakan commented 4 years ago

So there's no need to change SVG? Let me know if I need to do something about it.

About the text, yes, please make a PR.

MuhammedZakir commented 4 years ago

So there's no need to change SVG? Let me know if I need to do something about it.

No. You may want to check it though.

https://github.com/MuhammedZakir/en.javascript.info/blob/master/1-js/06-advanced-functions/03-closure/10-make-army/task-while-lexenv-makearmy.svg

About the text, yes, please make a PR.

Ok. I will do it later.

iliakan commented 4 years ago

To check this SVG for what? Sorry, trying to understand.

MuhammedZakir commented 4 years ago

To check this SVG for what? Sorry, trying to understand.

It's okay! :-) Like I said earlier, I could not extract strings from my image. That means, it can't be translate even if someone want to. So I wanted you to check that image to see if there is a need for translation or not. If there is no need, then everything is okay. Else, we will have to create a new image, so that the strings can be extracted for translation.