the scoping effect of the initialization block can be understood as if the declaration happens within the loop body, but just happens to be accessible within the condition and update parts.
const { log } = console;// ╭── i ──╮ <--- ⭐ `i` is "block-scoped"for (let i =0; i <3; i++) {// ⭐ every time "block scope" is entered,// a new/different `i` is captured by closure.//// ╭─ closure ──╮setTimeout( () =>log(i) ,1000); // 0, 1, 2 ⭐ }let j =0; // ❗ `j` is in "outer scope"for ( ; j <3; j++) {// ❗ every closure is capturing the same `j` variable//// ╭─ closure ──╮setTimeout( () =>log(j) ,2000); // 3, 3, 3 ❗ (j = 3 after for-loop)}var keep1 = [];// ╭── i ──╮ <--- ❗ "var" has no block scopefor (var i =0; i <3; i++) {// ❗ every closure is keeping the "same" `i`. keep1[i] =functionkeepI(){return i; };}var keep2 = [];// ╭── i ──╮ <--- ⭐ "let" is block-scopedfor (let i =0; i <3; i++) {// ⭐ `i` is block-scoped, every time block scope is entered,// a new/different `i` is created & kept by closure. keep2[i] =functionkeepI(){return i; };}[keep1.map(f =>f()),// ❗ [ 3, 3, 3 ]keep2.map(f =>f()),// ⭐ [ 0, 1, 2 ]].forEach(x =>log(x));