Javascript:在setTimeout中使用更改全局变量

时间:2014-04-25 13:42:08

标签: javascript

我正在使用Javascript并使用 firefox scratchpad 执行它。我有一个全局索引,我想在我的setTimeout(或任何异步执行的函数)中获取。我无法使用Array.push,因为数据的顺序必须保持,就像它是按顺序执行一样。这是我的代码: -

function Demo() {
    this.arr = [];
    this.counter = 0;
    this.setMember = function() {
        var self = this;

        for(; this.counter < 10; this.counter++){
            var index = this.counter;
            setTimeout(function(){
                self.arr[index] = 'I am John!';
            }, 100);
        }
    };
    this.logMember = function() {
        console.log(this.arr);
    };
}

var d = new Demo();
d.setMember();

setTimeout(function(){
    d.logMember();
}, 1000);

在这里,我希望我的d.arr有0到9个索引,所有索引都有'I am John!',但只有第9个索引有'I am John!'。我想,将this.counter保存到index局部变量中会拍摄this.counter的快照。任何人都可以帮我理解我的代码有什么问题吗?

4 个答案:

答案 0 :(得分:7)

这种情况下的问题与JS中的作用域有关。 由于没有块范围,它基本上等同于

this.setMember = function() {
    var self = this;
    var index;

    for(; this.counter < 10; this.counter++){
        index = this.counter;
        setTimeout(function(){
            self.arr[index] = 'I am John!';
        }, 100);
    }
};

当然,由于赋值是异步的,循环将运行完成,将index设置为9.然后该函数将在100ms后执行10次。

有几种方法可以做到这一点:

  1. IIFE(立即调用函数表达式)+闭包

    this.setMember = function() {
        var self = this;
        var index;
    
        for(; this.counter < 10; this.counter++){
            index = this.counter;
            setTimeout((function (i) {
                return function(){
                    self.arr[i] = 'I am John!';
                }
            })(index), 100);
        }
    };
    

    这里我们创建一个匿名函数,立即用索引调用它,然后返回一个将执行赋值的函数。 index的当前值在封闭范围内保存为i,分配正确

  2. 与1类似但使用单独的方法

    this.createAssignmentCallback = function (index) {
        var self = this;
        return function () {
             self.arr[index] = 'I am John!';
        };
    };
    
    this.setMember = function() {
        var self = this;
        var index;
    
        for(; this.counter < 10; this.counter++){
            index = this.counter;
            setTimeout(this.createAssignmentCallback(index), 100);
        }
    };  
    
  3. 使用Function.prototype.bind

    this.setMember = function() {
        for(; this.counter < 10; this.counter++){
            setTimeout(function(i){
                this.arr[i] = 'I am John!';
            }.bind(this, this.counter), 100);
        }
    };
    

    由于我们所关心的只是在函数中得到了正确的i,我们可以使用bind的第二个参数,它部分地应用一个函数来确保它被调用以后用当前指数。我们也可以删除self = this行,因为我们可以直接绑定被调用函数的this值。我们当然也可以摆脱索引变量并直接使用this.counter,使其更加简洁。

  4. 我个人认为第三种解决方案是最好的。 它简洁,优雅,完全符合我们的需要。 其他一切都更难以完成语言当时不支持的事情。 由于我们有bind,因此没有更好的方法来解决这个问题。

答案 1 :(得分:4)

setTimeout没有像您期望的index快照。所有超时都会将索引视为最后一次迭代,因为循环在超时之前完成。您可以将其包装在闭包中并传入索引,这意味着闭包中的索引不会受到全局index的任何更改。

(function(index){
    setTimeout(function(){
        self.arr[index] = 'I am John!';
    }, 100);
})(index);

答案 2 :(得分:0)

原因是,当settimeout开始时,for循环结束,执行索引值为9,所以所有的定时器基本上都是设置arr [9]。

答案 3 :(得分:-2)

之前的答案是正确的,但提供的源代码是错误的,有一个错误的小精灵代替自我。解决方案有效。

另一种没有闭包的方法是将index参数添加到setTimeout语句中的函数声明

function Demo() {
    this.arr = new Array();
    this.counter = 0;
    this.setMember = function() {
        var self = this;

        for(; this.counter < 10; this.counter++){
            var index = this.counter;
            setTimeout(function(){
                self.arr[index] = 'I am John!';
            }(index), 100);
        }
    };
    this.logMember = function() {
        console.log(this.arr);
    };
}

var d = new Demo();
d.setMember();

setTimeout(function(){
    d.logMember();
}, 1000);