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

Pra*_*hra 5 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);
Run Code Online (Sandbox Code Playgroud)

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

The*_*eme 8

这种情况下的问题与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);
    }
};
Run Code Online (Sandbox Code Playgroud)

当然,由于赋值是异步的,循环将运行完成,将索引设置为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);
        }
    };
    
    Run Code Online (Sandbox Code Playgroud)

    这里我们创建一个匿名函数,立即用索引调用它,然后返回一个将执行赋值的函数.当前值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);
        }
    };  
    
    Run Code Online (Sandbox Code Playgroud)
  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);
        }
    };
    
    Run Code Online (Sandbox Code Playgroud)

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

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


MrC*_*ode 5

setTimeout不具备的快照index,你期待。所有超时都会将索引视为最终迭代,因为您的循环在超时触发之前完成。您可以将它包装在一个闭包中并传入索引,这意味着闭包中的索引不受对 global 的任何更改的保护index

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