开发者问题收集

无法在 setTimeout 内正确访问数组元素

2012-11-28
987

这就是我要做的事情。 我有一个如下数组

var my_array = ['1', '2', '3' ... ,'1000000000000000'];

我想要做的是为该数组的每个元素创建一堆 HTML 元素,并且由于数组可以包含大量元素,因此我尝试执行以下操作,以便浏览器不会冻结。

for(var i in my_array)
{
    if(my_array.hasOwnProperty(i))
    {
       setTimeout(function(){
             do_something_with_data(my_array[i]);
       });
    }
 }

但是,setTimeout 中的 my_array[i] 没有它应有的值。

更准确地说,当我尝试 console.log(my_array[i]) 时,我得到的是这样的:

"getUnique" function (){
   var u = {}, a = [];
   for(var i = 0, l = this.length; i < l; ++i){
      if(u.hasOwnProperty(this[i])) {
         continue;
      }
      a.push(this[i]);
      u[this[i]] = 1;
   }
   return a;
}

getUnique 是我添加到 Array 原型的一个函数,就像这个:

Array.prototype.getUnique = function(){
   var u = {}, a = [];
   for(var i = 0, l = this.length; i < l; ++i){
      if(u.hasOwnProperty(this[i])) {
         continue;
      }
      a.push(this[i]);
      u[this[i]] = 1;
   }
   return a;
};

有人可以帮我解决这个问题吗?

3个回答

setTimeout 在循环完成后执行,并且 i 是此时的最后一个键或一些垃圾值。您可以像这样捕获 i:

for (var i in my_array) {
    if (my_array.hasOwnProperty(i)) {
        (function(capturedI) {
            setTimeout(function() {
                do_something_with_data(my_array[capturedI]);
            });
        })(i);
    }
}

您也不应该对数组使用 for..in 循环,因为它比 for 循环慢一个数量级(尤其是在使用 .hasOwnProperty 检查时),并且迭代顺序未定义

如果您有 jQuery 或愿意为旧版浏览器添加一些额外的代码,您可以执行以下操作:

my_array.forEach( function( item ) {
     setTimeout( function() {
         do_something_with_data( item );
     }, 1000);
});

使用 jQuery:

$.each( my_array, function( index, item ) {
     setTimeout( function() {
         do_something_with_data( item );
     }, 1000);
});

请参阅 [].forEach

的文档
Esailija
2012-11-28

问题是,您创建的函数对 i 变量有一个 引用 ,而不是其 的副本,因此在运行时,它们看到的是当时的 i (大概是超出了数组的末尾)。(更多信息: 闭包并不复杂

我建议采用一种完全不同的方法(见下文),但首先,让我们看看如何使您现有的方法发挥作用。

要使用 for 循环执行您要执行的操作,您必须让函数在不会改变的某些东西上闭包。通常的做法是使用工厂函数来创建超时函数,以便它们关闭工厂的参数。或者实际上,您可以传入数组元素的值而不是索引变量。

for(var i in my_array)
{
    if(my_array.hasOwnProperty(i))
    {
       setTimeout(makeFunction(my_array[i]));
    }
 }

function makeFunction(entry) {
    return function(){
        do_something_with_data(entry);
    };
}

但是 ,我可能会重新构建代码,这样您就不会不必要地创建大量的函数对象。相反,使用 一个 函数,并让它关闭它递增的索引:

// Assumes `my_array` exists at this point, and that it
// has at least one entry
var i = 0;
setTimeout(tick, 0);
function tick() {
    // Process this entry
    if (my_array.hasOwnProperty(i)) {
        do_something_with_data(my_array[i]);
    }

    // Move to next
    ++i;

    // If there are any left, schedule the next tick
    if (i < my_array.length) {
        setTimeout(tick, 0);
    }
}
T.J. Crowder
2012-11-28

这只是猜测。尝试一下:

for(var i in my_array)
{
    if(my_array.hasOwnProperty(i))
       setTimeout("do_something_with_data('"+my_array[i]+"')", 500);
}
Akhil Sekharan
2012-11-28