getify / You-Dont-Know-JS

A book series on JavaScript. @YDKJS on twitter.
Other
179.29k stars 33.48k forks source link

Shadowing weird behavior (Scope & Closures - 2nd Edition-Appendix A) #1723

Closed SeyyedKhandon closed 3 years ago

SeyyedKhandon commented 3 years ago

Edition: (1st or 2nd)** 2nd Edition

Book Title: Scope & Closures

Chapter: Appendix A: Exploring Further

Section Title: Parameter Scope

Question: As you said there is a parameter scope that can be shadowed: I changed a little bit the code, to see that there is actually a parameter scope(just add id++):

function whatsTheDealHere(id, defaultID = () => id++) {
  var id;
  console.log(`local variable 'id': ${id}`);
  console.log(`parameter 'id' (closure): ${defaultID()}`);
  console.log("reassigning 'id' to 5");
  id = 5;
  console.log(`local variable 'id': ${id}`);
  console.log(`parameter 'id' (closure): ${defaultID()}`);
}
whatsTheDealHere(10);
/*output:
local variable 'id': 10
parameter 'id' (closure): 10
reassigning 'id' to 5
local variable 'id': 5
parameter 'id' (closure): 11
*/

As we expected, it works as well as the book told us. let's change it a little bit further(change it to object):

function whatsTheDealHere(id, defaultID = () => {id.x++;return id;}) {
  var id;
  console.log(`local variable 'id': ${id.x}`);
  console.log(`parameter 'id' (closure): ${defaultID().x}`);
  console.log("reassigning 'id' to 5");
  id.x = 5;
  console.log(`local variable 'id': ${id.x}`);
  console.log(`parameter 'id' (closure): ${defaultID().x}`);
}
whatsTheDealHere({ x: 10 });
/*output:
local variable 'id': 10
parameter 'id' (closure): 11
reassigning 'id' to 5
local variable 'id': 5
parameter 'id' (closure): 6
*/

And when we use assignment for the whole id:

function whatsTheDealHere(id, defaultID = () => {id.x++;return id;}) {
  var id;
  console.log(`local variable 'id': ${id.x}`);
  console.log(`parameter 'id' (closure): ${defaultID().x}`);
  console.log("reassigning 'id' to 5");
  id = {x:5};
  console.log(`local variable 'id': ${id.x}`);
  console.log(`parameter 'id' (closure): ${defaultID().x}`);
}
whatsTheDealHere({ x: 10 });
/*output:
local variable 'id': 10
parameter 'id' (closure): 11
reassigning 'id' to 5
local variable 'id': 5
parameter 'id' (closure): 12
*/

Now we can assume that parameter scope is the parent scope, but here we cannot use const for it:

function whatsTheDealHere(id, defaultID = () => {id.x++;return id;}) {
//   var id;
  console.log(`local variable 'id': ${id.x}`);
  console.log(`parameter 'id' (closure): ${defaultID().x}`);
  console.log("reassigning 'id' to 5");
  const id = {x:5};
  console.log(`local variable 'id': ${id.x}`);
  console.log(`parameter 'id' (closure): ${defaultID().x}`);
}
whatsTheDealHere({ x: 10 });
//Uncaught SyntaxError: Identifier 'id' has already been declared

Why is this happening?(sorry for long question)

vsemozhetbyt commented 3 years ago

It is because of the difference between primitive values and objects. In the first case, you have two different variables with the same name and different primitive values. In the second case, you have two different variables with the same name and THE SAME object as their values, as objects are assigned by reference, not value:

function whatsTheDealHere2(id, defaultID = () => { id.x++; return id; }) {
  var id;
  console.log(id === defaultID()); // true, same object (objects are compared by identity, not by value)
}
whatsTheDealHere2({ x: 10 });

So when you change the object content in one scope, it is also changed in all scopes that reference this object.

See more, for example, here: https://javascript.info/object-copy

SeyyedKhandon commented 3 years ago

I know about the reference, but I thought that parameter scope, is an outer scope so we can redeclare it, but it seems that is not the case.

It is because of the difference between primitive values and objects. In the first case, you have two different variables with the same name and different primitive values. In the second case, you have two different variables with the same name and THE SAME object as their values, as objects are assigned by reference, not value:

function whatsTheDealHere2(id, defaultID = () => { id.x++; return id; }) {
  var id;
  console.log(id === defaultID()); // true, same object (objects are compared by identity, not by value)
}
whatsTheDealHere2({ x: 10 });

So when you change the object content in one scope, it is also changed in all scopes that reference this object.

See more, for example, here: https://javascript.info/object-copy

vsemozhetbyt commented 3 years ago

It seems that var id; is a no-op (otherwise the id would be undefined), as with any repeated var in the same scope.

getify commented 3 years ago

It seems that var id; is a no-op (otherwise the id would be undefined)

Quoting from that appendix section:

The strange bit here is the first console message. At that moment, the shadowing id local variable has just been var id declared, which Chapter 5 asserts is typically auto-initialized to undefined at the top of its scope. Why doesn't it print undefined?

In this specific corner case (for legacy compat reasons), JS doesn't auto-initialize id to undefined, but rather to the value of the id parameter (3)!

SeyyedKhandon commented 3 years ago

image

It make sense, if we consider parameter scope as an outer scope, but the problem here is, we cannot re-declare it:

function whatsTheDealHere(id,defaultID = () => id) {
    let id = 5;
    console.log( defaultID() );
}

whatsTheDealHere(3);
// Uncaught SyntaxError: Identifier 'id' has already been declared

It seems that var id; is a no-op (otherwise the id would be undefined)

Quoting from that appendix section:

The strange bit here is the first console message. At that moment, the shadowing id local variable has just been var id declared, which Chapter 5 asserts is typically auto-initialized to undefined at the top of its scope. Why doesn't it print undefined? In this specific corner case (for legacy compat reasons), JS doesn't auto-initialize id to undefined, but rather to the value of the id parameter (3)!

getify commented 3 years ago

Yes, TC39 intentionally created exceptions/violations to the mental model, for reasons. Shrugs.

SeyyedKhandon commented 3 years ago

Yes, TC39 intentionally created exceptions/violations to the mental model, for reasons. Shrugs.

So, this is somehow like an intentional bug e.g. something like the typeof null?

getify commented 3 years ago

It's not a bug. It's just not as clean of a mental metaphor as you might like. Crazy nuanced stuff like this is on a list of leaky/corner cases with like a million other things. Like I said before: shrug.

My advice in this section is, don't keep digging and exploiting the ugly nuances. Know that they exist so you're not confused, but stay away from them if you can.

Jacobatran commented 3 years ago

How did you get so good at programming Kyle? Somehow I end up on this thread of email and I don’t know how or why. Hello I am new here and I’m just starting to learn javascript.

On Sat, Feb 6, 2021 at 7:36 PM Kyle Simpson notifications@github.com wrote:

It's not a bug. It's just not as clean of a mental metaphor as you might like. Crazy nuanced stuff like this is on a list of leaky/corner cases with like a million other things. Like I said before: shrug.

My advice in this section is, don't keep digging and exploiting the ugly nuances. Know that they exist so you're not confused, but stay away from them if you can.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/getify/You-Dont-Know-JS/issues/1723#issuecomment-774587418, or unsubscribe https://github.com/notifications/unsubscribe-auth/AIYGJ27YMIHGXA3JIOPQ4T3S5YDE7ANCNFSM4XGDNWLA .

getify commented 3 years ago

If you spend 25 years coding and teaching others what you learned, you'll probably get even further than I have. ;-)

Jacobatran commented 3 years ago

I have no idea honestly but it’s great to know that I am talking to you through an email. It’s a pleasure, and I’m happy that I see your reply for some reason but I started reading your get started book in chapter 1 and I’m lost already. So, just be patient and allow myself to learn? I could barely grab the concept of a loop around my head last week but I’m slowly getting there and learning how to use it in very basic problem-solving question

On Sat, Feb 6, 2021 at 7:49 PM Kyle Simpson notifications@github.com wrote:

If you spend 25 years coding and teaching others what you learned, you'll probably get even further than I have. ;-)

— You are receiving this because you commented.

Reply to this email directly, view it on GitHub https://github.com/getify/You-Dont-Know-JS/issues/1723#issuecomment-774588695, or unsubscribe https://github.com/notifications/unsubscribe-auth/AIYGJ266BMTK52TBCJWKWQDS5YEUNANCNFSM4XGDNWLA .

getify commented 3 years ago

You're posting to an issue thread on the github repo. I guess you're seeing email notifications from github because you're "watching" the repo.