无法在 setTimeout 内设置 innerHTML

Posted

技术标签:

【中文标题】无法在 setTimeout 内设置 innerHTML【英文标题】:Cannot set innerHTML inside setTimeout 【发布时间】:2020-01-03 01:18:50 【问题描述】:

尝试设置一个 html 类的 innerHTML,它们是四个框,每个框依次设置 ​​3 秒。当 innerHTML 设置为加载图标时,我可以在没有 setTimeout 的情况下设置 innerHTML。当将 innerHTML 放入 setTimeout 时,将返回以下内容:'Uncaught TypeError: Cannot set property 'innerHTML' of undefined'。

尝试调试我的代码,将消息发送到控制台并搜索 ***,但没有成功。

var x = document.getElementsByClassName("numberBox");


for (var i = 0; i < 4; i++) 
    x[i].innerHTML= '';
    x[i].innerHTML= "<div class='loader'></div>"


// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();


for (var i = 0; i < 4; i++) 
    setTimeout(function () 
        x[i].innerHTML= '';
        x[i].innerHTML = randomNums[i];
    , 3000 * i);

想知道为什么我的 innerHTML 不能在此处设置在 setTimeout 内以及我的问题的可能解决方案。

【问题讨论】:

理解 adr5240 指出的闭包和 setTimeout 这些答案的简短版本是 var 很糟糕而且很糟糕,您应该始终改用 let 【参考方案1】:

我认为这是当前范围的问题。 setTimeout 函数创建自己的范围,它不引用旧变量。您可能需要重新定义超时内的 x 或将数组显式传递给超时。

请参阅此处了解操作方法:How can I pass a parameter to a setTimeout() callback?

我还建议您阅读闭门器:https://developer.mozilla.org/en-US/docs/Web/javascript/Closures

【讨论】:

【参考方案2】:

当您在 for 循环中使用 var 时,setTimeout 实际上会为 i 的最后一个值触发,就像在 var 中一样,绑定只发生一次。

这是因为setTimeout在整个循环完成时被触发,那么你的i将是4。请记住,由于您在 setTimeout 调用中传递的回调函数,所以存在一个闭包。该闭包现在将引用 i 的最终值,即 4。

因此,在这种情况下,当整个循环执行完毕时,i 的值是 4,但 x 中的索引直到 3。这就是为什么当您尝试访问 x[4] 时,您会看到 undefined 并看到 TypeError 来解决此问题,只需使用 let 在每次迭代中使用 i 的新值重新绑定:

for (let i = 0; i < 4; i++) 
    setTimeout(function () 
        x[i].innerHTML= '';
        x[i].innerHTML = randomNums[i];
    , 3000 * i);

此外,如果由于浏览器不兼容而无法使用 let,您可以使用 IIFE 来解决问题:

for (var i = 0; i < 4; i++) 
      (function(i)
         setTimeout(function () 
           x[i].innerHTML= '';
           x[i].innerHTML = randomNums[i];
         , 3000 * i);
       )(i);
 

这是可行的,因为var 具有函数作用域,因此在每次迭代中,都会创建一个新作用域以及与i 的新值绑定的函数。

【讨论】:

【参考方案3】:

由于缺少 i 参数 - 超时可能最后使用 (4) 超出数组。

但您可以通过添加下一个超时参数来设置和使用函数参数。

var x = document.getElementsByClassName("numberBox");


for (var i = 0; i < 4; i++) 
    x[i].innerHTML= '';
    x[i].innerHTML= "<div class='loader'></div>"


// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();


for (var i = 0; i < 4; i++) 
    setTimeout(function (i) 
        x[i].innerHTML= '';
        x[i].innerHTML = randomNums[i];
    , 3000 * i, i);


function generateRandomNumbers() 
var retVal = [];
for (var i = 0; i < 4; i++) 
retVal.push(Math.random());

return retVal;
<div class="numberBox"></div>
<div class="numberBox"></div>
<div class="numberBox"></div>
<div class="numberBox"></div>

【讨论】:

【参考方案4】:
    // function to generate array of 4 random numbers
    var x = document.getElementsByClassName("numberBox");
    var randomNums = generateRandomNumbers();
    for (var i = 0; i < 4; i++) 
        setTimeout(function () 
            x[i].innerHTML= '';
            x[i].innerHTML = randomNums[i];
        , 3000 * i, x);
    

【讨论】:

【参考方案5】:

这个问题与一个非常基本且流行的 Javascript 概念有关,称为closure。至少可以通过两种方式解决:

    使用let
var x = document.getElementsByClassName("numberBox");


for (let j = 0; j < 4; j++) 
    x[j].innerHTML= '';
    x[j].innerHTML= "<div class='loader'></div>"


// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();


for (let i = 0; i < 4; i++) 
    setTimeout(function () 
        x[i].innerHTML= '';
        x[i].innerHTML = randomNums[i];
    , 3000 * i);

    使用 IIFE

var x = document.getElementsByClassName("numberBox");


for (var i = 0; i < 4; i++) 
    x[i].innerHTML= '';
    x[i].innerHTML= "<div class='loader'></div>"


// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();


for (var i = 0; i < 4; i++) 
    setTimeout((function (j) 
        x[j].innerHTML= '';
        x[j].innerHTML = randomNums[j];
    )(i), 3000 * i);


【讨论】:

【参考方案6】:

您正在尝试在内部方法中访问 x,即 x 未在 setTimeout 的范围内定义,这就是您收到该执行的原因

我建议您使用setInterval 函数作为解决方案 你的代码:

var randomNums = generateRandomNumbers(); 

for (var i = 0; i < 4; i++)  

      setTimeout(function ()  
         x[i].innerHTML= ''; 
         x[i].innerHTML = randomNums[i]; 
     , 3000 * i); 

解决办法

var randomNums = generateRandomNumbers();
let i = 0;
let interval = setInterval(function() 
            if( i != 2)
                    x[i].innerHTML= ''; 
                x[i].innerHTML = randomNums[i];
                    i += 1;
             else 
                    clearInterval(interval) ;
            
, 3000);

【讨论】:

【参考方案7】:

这个问题似乎是由几个因素造成的。

setTimeout() 范围之外定义generateRandomNumbers() 函数。 在for 循环中使用var 定义。
function generateRandomNumbers() 
    return Math.floor(Math.random() * 999999) + 10000;


var x = document.getElementsByClassName("numberBox");

for (var i = 0; i < x.length; i++) 
    x[i].innerHTML= '';
    x[i].innerHTML= "<div class='loader'></div>"


for (let i = 0; i < x.length; i++) 
    setTimeout(function() 
      x[i].innerHTML= '';
      x[i].innerHTML = generateRandomNumbers();
    , 3000 * i);

这是一个不同的实现,但这里是 Fiddle

【讨论】:

以上是关于无法在 setTimeout 内设置 innerHTML的主要内容,如果未能解决你的问题,请参考以下文章

循环内的 setTimeout() 方法

使用 Node 进行 Jest 测试 - 超时 - 在 jest.setTimeout 指定的 5000 毫秒超时内未调用异步回调

无法在具有匿名功能的函数内使用“this”

超时 - 在 jest.setTimeout 指定的 5000 毫秒超时内未调用异步回调

超时 - 在 jest.setTimeout 指定的 5000 毫秒超时内未调用异步回调

Got Timeout - 在 jest.setTimeout 指定的 5000 毫秒超时内未调用异步回调