Reputation: 3
I'm actually stuck solving this problem. It gives me this error "Uncaught TypeError: Cannot read property 'play' of undefined". When i used foreach loop everything goes fine but using normal for-loop gives me this error and i don't know why.
here is the code using javascript
window.addEventListener("load", function () {
const pads = document.querySelectorAll(".pads div");
const sounds = document.querySelectorAll(".sound");
var numberOfPads = document.querySelectorAll(".pads div").length;
for (var i = 0; i < numberOfPads; i++) {
pads[i].addEventListener("click", function () {
sounds[i].play();
});
}
});
HTML CODE:
<body>
<section class="app">
<header>
<h1>Beatmaker</h1>
</header>
<div class="visual">
<div class="pads">
<div class="pad1">
<audio class="sound" src="sounds/bubbles.mp3">
</audio>
</div>
<div class="pad2">
<audio class="sound" src="sounds/clay.mp3">
</audio>
</div>
<div class="pad3">
<audio class="sound" src="sounds/confetti.mp3">
</audio>
</div>
</div>
<div class="pads">
<div class="pad4">
<audio class="sound" src="sounds/glimmer.mp3">
</audio>
</div>
<div class="pad5">
<audio class="sound" src="sounds/moon.mp3">
</audio>
</div>
<div class="pad6">
<audio class="sound" src="sounds/ufo.mp3">
</audio>
</div>
</div>
</div>
</section>
</body>
Upvotes: 0
Views: 2338
Reputation: 29012
This is a tricky one. It happens because i
is function-scoped, meaning that one and the same i
is used for any point inside your function. This also applies to things running in the future. The loop modifies i
, and when the loop is done, i
will be 1 higher than the maximum value.
Let's go through this step by step - in the order in which things happen. For clarity: I see 6 divs, so we have valid values of i
from 0 to 5 (including).
load
event handler executes:
i
is 0. Your loop body runs because 0 is less than 6.pads[i].addEventListener("click", ...)
executes with i == 0
i
is 1. Your loop body runs because 1 is less than 6.pads[i].addEventListener("click", ...)
executes with i == 1
i
is 5. Your loop body runs because 5 is less than 6.pads[i].addEventListener("click", ...)
executes with i == 5
i
is 6. Your loop body doesn't run because 6 is not less than 6.pad4
.
click
handler runs (the one that was set when i
was 3).i
here, i
is now 6 (see the last step from before!).sounds[i].play()
runs, attempting to access sounds[6]
sounds[6]
is undefined, you cannot read its property play
and you get an error.The reason it works with a forEach
is that it uses a callback, and that means that the i
you access in there is the argument to your callback function, which is local to the specific function invocation. So you essentially get a new i
every time, which keeps its value also in the future (it is never modified). You essentially get 6 different i
s this way, not 1 i
that changes its value over time!
If you want to keep using a for
loop, you have to use let
instead of var
:
for (let i = 0; i < numberOfPads; i++) {
// ...
}
This is because let
creates a block-scoped variable. I know it is a bit confusing now how this can work, since i
is still getting modified, but this is due to a special convenience behavior with for
in combination with let
: It actually creates two i
s here, one that is modified every time (the one you see inside the for (...)
parens) and one that is created as copy from the modified one on every iteration (the one you see inside the loop body).
If you are getting confused by this, another way to (more logically) solve the problem would be to manually assign i
to a block-scoped copy inside the loop:
window.addEventListener("load", function () {
const pads = document.querySelectorAll(".pads div");
const sounds = document.querySelectorAll(".sound");
var numberOfPads = document.querySelectorAll(".pads div").length;
for (var i = 0; i < numberOfPads; i++) {
const j = i; // Create local copy
pads[j].addEventListener("click", function () {
sounds[j].play(); // Now we use j here and not i
});
}
});
(I used const
to emphasize that it won't and even can't be modified, but let
would work as well. Just var
won't!)
Upvotes: 1