for-Schleife mit let?

4 Antworten

Vom Fragesteller als hilfreich ausgezeichnet

Bei jedem Durchlauf der Schleife wird jeweils ein neuer Scope für das Statement erzeugt. Damit hat die Änderung der Variable in einem neuen Durchlauf keinen Einfluss auf den Scope des vorherigen Durchlaufs.

In deinem Beispiel werden dann 5 Scopes in der Schleife und noch mal 5 Kind-Scopes durch die Arrow-Function erzeugt.

Anders sieht es aus, wenn du es so schreibst:

let i = 0;

for (; i < 5; i++) { … }

Dann werden zwar auch immer neue Scopes erzeugt, da jedoch i schon außerhalb der for-Scopes erzeugt wurde, wird trotzdem immer 5 ausgegeben.

Woher ich das weiß:Berufserfahrung – Entwickle Webseiten seit über 25 Jahren.
MrAmazing2 
Fragesteller
 14.01.2024, 00:28

Ah ok, danke, das erklärt das unterschiedliche Verhalten bei block-scoped Variablen 👍🏼

Damit hat die Änderung der Variable in einem neuen Durchlauf keinen Einfluss auf den Scope des vorherigen Durchlaufs.

Aber Einfluss auf die Variable im Scope des nächsten Durchlaufs hat es (siehe @ultrarunner‘s Antwort) hingegen schon, oder?

Heißt es kopiert beim Erstellen des nächsten Scopes immer das i vom vorherigen Scope?

1
Babelfish  14.01.2024, 11:18
@MrAmazing2

Wie das ganz genau bei Javascript funktioniert, kann ich dir leider auch nicht sagen. Ich denke mal, dass beim ersten Durchlauf der Scope erzeugt wird und das erste Statement (let i = 0) ausgeführt wird. Bei jedem neuen Durchlauf wird vermutlich der Scope dupliziert und das weitere Statement (i++) ausgeführt.

Kann aber auch sein, dass immer ein neuer Scope erzeugt wird. Da müsste man mal die ECMA-Spezifikation lesen aber nicht am Sonntag. ✌️

1
MrAmazing2 
Fragesteller
 16.01.2024, 00:06
@Babelfish

Hatte recht mit meiner Vermutung, dass das einfach das i aus dem vorherigen Durchgang kopiert. Aber auch nur, wenn das "let i" im Schleifen-Header gemacht wurde, wie du schon richtig bemerkt hast. Interesting.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for#lexical_declarations_in_the_initialization_block

if initialization is a let declaration, then every time, after the loop body is evaluated, the following happens:
1. A new lexical scope is created with new let-declared variables.
2. The binding values from the last iteration are used to re-initialize the new variables.

Als ob da jemand von alleine drauf kommt, dass das so gehandelt wird...
Ist zwar nützlich, aber imo absolut unintuitiv :D

1

JavaScript behandelt die Schleifendurchläufe scheinbar nicht als einen Scope, sondern wie als wären es mehrere Scopes nacheinander und der Schleifenkopf gehört dazu. Die Variable wird also nicht vor der Schleife deklariert (wie bei der while-Schreibweise), sondern jedes Mal bei jedem Schleifendurchlauf erneut. Das passt auch dazu, dass die Variable ja block-scoped ist, da ist es naheliegend, dass der Interpreter den einfachsten Weg geht, um dieses Verhalten zu erreichen: Jedes Mal neu deklarieren.

So zumindest meine von Halbwissen gespickte Erklärung zu den Interpreter-Internals von JavaScript :D Eine Quelle, die genau dieses Verhalten beschreibt, ist mir nicht bekannt.

Aber Du darfst es auch nicht mit C# vergleichen, da wird der Code "gelowered", bei JavaScript nicht. Das braucht es vermutlich auch nicht, da ein Interpreter ja in vielen Punkten einfacher arbeiten kann, als ein Compiler. Man kann die for-Schleife also nicht 1zu1 als while-Schleife umschreiben, bei C# wäre das technisch identisch, bei JavaScript sind das aber immer noch zwei verschiedene Dinge und werden auch verschieden ausgeführt.

Woher ich das weiß:Berufserfahrung – C#.NET Senior Softwareentwickler
MrAmazing2 
Fragesteller
 16.01.2024, 00:19

Der "einfachste Weg" für den Interpreter wäre wohl eher

for (let i = 0; i < 5; i++) { … }

einfach wie

{
  let i = 0;
  for (; i < 5; i++) { … }
}

zu behandeln, wie man es auch erwarten würde, wenn man for-Schleifen aus anderen Sprachen kennt. Initialisierung wird einmal ausgeführt und fertig. Or not? :D

Aber nope, die haben da ein fettes extra Handling dafür eingebaut, dass wenn ein "let" in der Schleife in der Initialisierungs-Phase gemacht wird, dass es diese let-Variablen dann jeden Durchgang neu erzeugt und mit dem Wert derselben Variable aus dem vorherigen Durchgang befüllt... https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for#lexical_declarations_in_the_initialization_block
Aber wenn das let außerhalb des Kopfes ist macht es das nicht, und wenn ein var da steht macht es das auch nicht...

Weiß ich nicht wie das "naheliegend" sein sollte, aber joa, guter Guess ... :D

0
Palladin007  16.01.2024, 13:42
@MrAmazing2

Vielleicht war das ja auch der Plan?
Ich weiß nicht, wie oft ich schon gesehen habe, dass Leute genau diesen Fehler gemacht und sich gewundert haben, warum die for-Schleife nicht funktioniert :D
So haben sie diesen Bug eliminiert - zumindest, solange man let benutzt.

1
let i = 0;“ wird doch nur ein einziges mal ausgeführt,

Die Schleife wird solange durch iteriert bis i gleich 5 oder größer ist. Durch i++ wird jedesmal um eins hoch addiert. Im zweiten Durchgang entspricht i nicht mehr 0, sondern 1 und ist damit weiterhin kleiner als Bedingung von 5. Ergo läuft die Schleife erneut durch und jedesmal den Wert der Variable i um eins erhöht.

Woher ich das weiß:Berufserfahrung – UI/UX Designer, Full-Stack Developer
MrAmazing2 
Fragesteller
 13.01.2024, 22:16

Jo, ich weiß wie 'ne Schleife funktioniert.

Aber warum macht die Schleife bei let etwas anderes als bei var , das checke ich nicht?

0
medmonk  13.01.2024, 22:18
@MrAmazing2

Das Problem ist, das var auf die globale Ebene gehoben wird. Genau das passiert bei let nicht, da dies auf den jeweiligen Block beschränkt wird. Ich würde var ohnehin nicht mehr verwenden, sondern nur mit let und const arbeiten.

0
MrAmazing2 
Fragesteller
 13.01.2024, 22:24
@medmonk

Aber die Deklaration von i ist doch auf einer höheren Ebene als die Schleifendurchläufe, weil die Deklaration eben nur einmal, nämlich VOR dem Ablauf der Schleife ausgeführt wird. Dann würde das i doch eigentlich wiederverwendet werden.

Hier klappts schließlich auch:

let i = 0;
while (i < 5) {
  i++;
}

Und wenn ich

let i = 2;
if (true) {
  i++;
}
console.log(i);

mache wird das i auch wie erwartet verändert, da innere Blöcke immer Zugriff auf äußere Blöcke haben.

Wie gesagt verstehe ich, dass let block-scoped ist. Aber die Schleifendurchläufe sind doch einfach nur ein Sub-Block, und verwenden somit die selbe Variable, oder nicht?

0
medmonk  13.01.2024, 22:46
@MrAmazing2

Let ist block-scoped und somit auf die Schleife begrenzt. Wenn die gleiche Variable jetzt nochmal außerhalb deklariert wird, hat sie zwar optisch die selbe Bezeichnung, stellt aber eine eigene (neue) Variable dar.

let i (global) oder let i in der Schleifen-Initialisierung sind nicht gleich - nicht dasselbe. Bei der while-Schleife wird ja keine neue Variable deklariert, sondern auf eine bestehende zugegriffen. Genau da liegt schlussendlich der Unterschied.

1
MrAmazing2 
Fragesteller
 15.01.2024, 22:51
@medmonk

Aber warum sind die nicht gleich?

Der Initializer wird doch nur einziges mal ausgeführt und nicht am Anfang jedes Schleifendurchlaufs - Wie kommt es dann, dass es dann für jeden Scope eine eigene Variable gibt, wo kommt die auf einmal her? :D
Wenn ich eine for-Schleife mit let i=100 habe, dann wurden ja quasi 100 verschiedene i's erstellt, obwohl das "let i = 100" nur einmal ausgeführt wurde. Wie kommt es dazu?

Bin durch Babelfisch's Antwort auf folgende mögliche Schlussfolgerung gekommen, stimmt die?:

Jeder Durchlauf einer Schleife hat seinen eigenen Scope.
Variablen, die in der Initialisierungs-Phase einer for-Schleife erstellt wurden, werden im jeweils nächsten Durchlauf der Schleife aus dem Scope des vorherigen Durchlaufs kopiert.

Dadurch folgt einerseits, dass jeder Durchlauf seine eigene Kopie von i hat, basierend auf der des vorherigen Durchlaufs. (Außer i wurde außerhalb der Schleifen-Initialisierung erstellt, dann wird sich das folglich geteilt.)

let i = 0;
for (; i < 5; i++) { … }

vs.

for (let i = 0; i < 5; i++) { … }

Andererseits erklärt das auch, warum ich im Durchlauf selbst ein "i++" machen kann und der nächste Scope diese Erhöhung dann übernimmt, obwohl jeder Durchlauf ja augenscheinlich sein eigenes i hat.

Nur die Frage mit dem var ist dann noch offen... Wenn es die Variable kopiert, wie kommt's, dass es bei var immer die selbe verwendet? Die werden da doch kein if-else eingebaut haben das je nach var oder let was anderes macht, das wär ja komplett bescheuert...

0
MrAmazing2 
Fragesteller
 15.01.2024, 23:37
@MrAmazing2

Ok, hab grad die entsprechende Doku in den Mozilla Docs gefunden... https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for#lexical_declarations_in_the_initialization_block

Es "wäre" nicht komplett bescheuert, es IST komplett bescheuert. Bei var wird die selbe Variable reused, bei let wird sie vom vorherigen Durchgang kopiert. Aber nur wenn die Initialisierung mit let im Schleifenkopf ist, andererseits wird auch nur die selbe Variable außerhalb reused. Da ist also wirklich ein "Wenn "let" im Schleifenkopf, dann spezielles handling" ....
Junge was für 'ne Scheiße, als ob da jemand von alleine draufkommt, dass das so funktioniert ...

Aber danke dir.

0
medmonk  17.01.2024, 07:38
@MrAmazing2
Der Initializer wird doch nur einziges mal ausgeführt und nicht am Anfang jedes Schleifendurchlaufs - Wie kommt es dann, dass es dann für jeden Scope eine eigene Variable gibt, wo kommt die auf einmal her? 

Ich habe mich da falsch resp. missverständlich ausgedrückt und eigentlich sagen wollte, dass die initialisierte Variable halt innerhalb der Schleife verwendet wird. Die Schleife iteriert halt solange durch, bis die Bedingungen erfüllt oder nicht mehr erfüllt wird (je nachdem was man wünscht und als Bedingung festgelegt hat).

Jeder Durchlauf einer Schleife hat seinen eigenen Scope.

Variablen, die in der Initialisierungs-Phase einer for-Schleife erstellt wurden, werden im jeweils nächsten Durchlauf der Schleife aus dem Scope des vorherigen Durchlaufs kopiert.

Es wird eher der Wert und weniger die Variable kopiert, sobald die Schleife mit dem iterieren beginnt. Andernfalls würde die Schleife ja sonst nie enden, weil die Bedingungen ohne neue Werte gar nicht erfüllt werden kann.

Nur die Frage mit dem var ist dann noch offen... Wenn es die Variable kopiert, wie kommt's, dass es bei var immer die selbe verwendet?

Die Frage ist relativ einfach beantwortet. In JavaScript besitzt eine mit „var” deklarierte Variable Funktions- oder globale Scoping-Regeln, während "let" Blockscoping einführt und nur in dessen Block sichtbar ist. Es gibt mittlerweile eigentlich so gut wie keinen Grund mehr Variablen mit „var” zu deklarieren.

Var ist aufgrund der globalen Scoping-Regeln sehr fehleranfällig und potenzielle Probleme gleich vermieden werden, wenn Variablen mit „let” eben nur in ihrem Geltungsbereich (Block) sichtbar sind. Deutlicher wird es, wenn du mal in Team an größeren Projekten mit mehreren tausend Zeilen Programmcode arbeitest.

  • Var → Globale Scoping-Regeln
  • Let → Lokale Scoping-Regeln (Blockscoping)

Was für dich bei alle dem vielleicht noch interessant sein könnte, ist eine „Immediately Invoked Function Expression” (kurz IIFE) - also eine anonyme selbstaufrufende Funktion mit abgeschlossenem Scope. Dabei handelt es um isolierte Umgebungen mit eigenem Scope, wodurch Änderungen von außen unterbunden werden.

(() => {
  // Dieser Code hat seinen eigenen Scope
})();

Durch die globalen Scoping-Regeln von „var” ist es auch davon abhängig, an welcher Stelle eine solche Variable gesetzt wird/wurde. Je nach dessen Position steht sie zur Verfügung oder halt nicht. Hauptmerkmal ist vor allem ihr globales Scoping, während let mit Blockscoping beschränkt wird.

Junge was für 'ne Scheiße, als ob da jemand von alleine draufkommt, dass das so funktioniert ...

Es ist keine „Scheiße” sondern eher eine Frage, ob man sich gescheit mit einer Sprache auseinandersetzt und dessen Dokumentation studiert. Bei anderen Dingen ist es auch nicht anders, sei es abweichende Syntax oder ähnliches. Mit am wichtigsten ist es zu verstehen, welche Probleme sich durch globale Variablen ergeben können und wie man gewisse Fehlerquellen vermeiden kann.

LG medmonk

0
Warum hat hier jeder Schleifen-Durchgang seine eigene Kopie von i?

Ist denn das so?

Du kannst durchaus innerhalb der Schleife das i verändern, und dieses veränderte i wird für den nächsten Durchlauf benutzt.

for (let i = 0; i < 5; i++)
{
  i++;
  console.log(i);
}

Das liefert:

1
3
5

… und somit dasselbe Ergebnis wie mit "var" statt "let".

MrAmazing2 
Fragesteller
 13.01.2024, 22:19

Warum printet

for (let i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 10);
}

dann nicht

5
5
5
5
5

sondern

1
2
3
4
5

?

0