问题是:无法读取未定义的属性“播放”

时间:2020-03-08 22:23:08

标签: javascript html

我实际上一直在解决这个问题。它给了我这个错误“未捕获的TypeError:无法读取未定义的属性'play'”。当我使用foreach循环时,一切都很好,但是使用普通的for循环给我这个错误,我不知道为什么。

这是使用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代码:

<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>

1 个答案:

答案 0 :(得分:0)

这是一个棘手的问题。发生这种情况的原因是 i是函数作用域的,这意味着一个相同的i用于函数中的任何点。这也适用于将来的事情。循环修改 i,完成循环后,i将比最大值高1。

让我们按照发生的顺序逐步进行此操作。为了清楚起见:我看到了6个div,因此我们有i的有效值,范围是0到5(包括)。

  • 您的load事件处理程序执行:
    • i为0。因为0小于6,所以循环体运行。
    • 语句pads[i].addEventListener("click", ...)i == 0一起执行
    • i为1。因为1小于6,所以循环体运行。
    • 语句pads[i].addEventListener("click", ...)i == 1一起执行
    • ...
    • i是5。因为5小于6,所以循环体运行。
    • 语句pads[i].addEventListener("click", ...)i == 5一起执行
    • i是6。您的循环正文运行,因为6不小于6。
  • 您单击一个div,说pad4
    • click处理程序运行(i为3时设置的处理程序)。
    • 但是,由于我们一直在这里谈论相同的i,所以 i现在是6 (请参阅之前的最后一步!)。
    • sounds[i].play()运行,试图访问sounds[6]
    • 由于sounds[6]未定义,因此您无法读取其属性play并收到错误消息。

它与forEach一起使用的原因是它使用了回调,这意味着您在其中访问的i是回调函数的参数,该参数对于具体功能调用。因此,您基本上每次都会获得一个新的i,这在将来也将保持其价值(它永远不会被修改)。这样,您基本上得到了6个不同的i,而不是1个随时间变化的i

如果您想继续使用for循环,则必须使用let而不是var

for (let i = 0; i < numberOfPads; i++) {
  // ...
}

这是因为let创建了一个块作用域变量。我知道现在它的工作方式有些混乱,因为i仍在修改中,但这是由于forlet结合使用时有特殊的便利性:实际上在此处创建两个i,一个每次都被修改(您在for (...)内部看到的一个),另一个在每次迭代时从修改后的副本创建为副本(您在内部看到的一个)循环体)。

如果对此感到困惑,另一种(更合乎逻辑)解决问题的方法是手动将i分配给循环内的块作用域副本:

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
        });

    }

});

(我用const来强调它不会被修改,甚至也不能被修改,但是let也是可以的。var不会!)