我正在使用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
的快照。任何人都可以帮我理解我的代码有什么问题吗?
答案 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次。
有几种方法可以做到这一点:
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
,分配正确
与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);
}
};
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
,使其更加简洁。
我个人认为第三种解决方案是最好的。
它简洁,优雅,完全符合我们的需要。
其他一切都更难以完成语言当时不支持的事情。
由于我们有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);