5

好吧,我不知道这到底是什么好标题,因为这是一种最特殊的情况,或者我异常愚蠢。

这就是我想要做的。

创建一个简单的<meter>标签,它是 HTML5 中的新标签。主要问题是我的javascript。我试图在我的javascript中逐渐增加meter标签的值。但不知何故,它不能按我想要的方式工作。

JavaScript。

for (var i = 0; i <= 10; i++) {
    var a = document.getElementById("mtr1");
    setTimeout(function () {
        console.log(i);
        a.value = i;
    }, 250);
}

我正在尝试每 250 毫秒逐渐增加仪表的值。这不会发生。相反,仪表直接跳到 10。

我感兴趣的i是我在控制台中获得的价值。我得到了10, 而不是 1,2,3...10 的实例。

为什么会这样?

小提琴

4

9 回答 9

7

这是 JavaScript 闭包的经典之作。这i是对变量的实际引用,而不是它的副本。在您遍历循环后,它的值为 10,这就是所有日志调用都将 10 写入日志的原因。
这应该会更好:

for (var i = 0; i <= 10; i++) {
    var a = document.getElementById("mtr1");
    setTimeout(function (i) {
        return function() {
            console.log(i);
            a.value = i;
        };
    }(i), 250 * i);
}

这里最内部i的是setTimeout的回调参数,而不是您在循环体中声明的变量。

于 2013-11-06T13:13:25.280 回答
7

您应该阅读更多关于 JavaScript 中的闭包的信息。当一个变量被关闭时,它是同一个变量,而不是一个副本。由于setTimeout是异步的,整个循环在任何函数运行之前完成,因此i变量将10无处不在。

演示

function incMtrAsync(max, delay, el) {
    if (el.value++ < max) {
        setTimeout(incMtrAsync.bind(null, max, delay, el), delay);
    }   
}

incMtrAsync(10, 250, document.getElementById("mtr1"));

上述实现使用递归方法实现循环。每次inMtrAsync调用时,它都会检查value仪表的 是否达到最大值,如果没有,则注册另一个超时并对其自身进行回调。

如果您还想延迟初始增量,只需将第一次调用包装在另一个超时中。

setTimeout(incMtrAsync.bind(null, 10, 250, document.getElementById("mtr1")), 250);
于 2013-11-06T13:20:27.673 回答
5

没有人使用 setInterval,所以这是我的解决方案(http://jsfiddle.net/Qh6gb/4/):

var a = document.getElementById("mtr1");
var i = 0;
var interval = setInterval(function () {
    console.log(i);
    a.value = ++i;
    if (i == 10) {
        clearInterval(interval);
    }
}, 250);
于 2013-11-06T13:15:10.520 回答
3

您描述的问题发生在setTimeout原始版本中的异步调用看到值之前10i因为这是执行回调时的值。

因此,这是闭包范围的问题,要使其工作,您应该像这样:

for (var i = 0; i <= 10; i++) {
    var a = document.getElementById("mtr1");
    (function (i, a) {
        setTimeout(function () {
          console.log(i);
          a.value = i;
        }, 250);
    })(i, a);
}

另外,由于a总是相同的,这应该更好:

var a = document.getElementById("mtr1");
for (var i = 0; i <= 10; i++) {
    (function (i) {
        setTimeout(function () {
          console.log(i);
          a.value = i;
        }, 250);
    })(i);
}

如果你想看到计数器“滴答作响”,这将使它可见:

var a = document.getElementById("mtr1");
for (var i = 0; i <= 10; i++) {
    (function (i) {
        setTimeout(function () {
          console.log(i);
          a.value = i;
        }, 1000 * i);
    })(i);
}

http://jsfiddle.net/LDt4d/

于 2013-11-06T13:15:15.117 回答
2

发生这种情况是因为您调用了 setTimeout,它是“异步的”。所以 setTimeout 被调用了 10 次,但是在整个循环完成之后,它就会被执行。因此,每次通话中 i = 10...

http://jsfiddle.net/Qh6gb/9/

有解决办法:

var i = 1,
    meter = document.getElementById("mtr1");

function increase() {
    meter.value = i++;
    console.log(i);
    if(i<=10) {
        setTimeout(increase, 250);
    }
}

setTimeout(increase, 250);
于 2013-11-06T13:11:41.643 回答
1

你可以使用 timeoutjquery插件:。更容易

但是你应该计算你的超时时间,

为你 ,timeout=250*max=250*10=2500

所以

$('meter').timeout(2500);

演示

于 2013-11-06T14:44:15.850 回答
0

在函数内部运行 for 循环,而不是在循环的每一步都声明一个闭包。

JSFIDDLE:http: //jsfiddle.net/Qh6gb/3/

var a = document.getElementById("mtr1");
setTimeout(function () {      
    for (var i = 0; i < 10; i++) {
        console.log(i);
        a.value = i;
    }
}, 250);
于 2013-11-06T13:12:55.323 回答
0

我希望我理解正确。请尝试告诉我您是否有解决方案。

var count = 0;
function increment(){
    document.getElementById("meter").value = count; 
    count++; 
    if(count ==10) 
        count=0;
}
setInterval(increment, 250);

请检查jsFiddle

于 2013-11-06T13:21:26.317 回答
-1

您正在创建多个同时启动的功能。将计时器乘以 i 以获得正确的延迟。

for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
setTimeout(function () {
    console.log(i);
    a.value = i;
}, 250 * i);
}
于 2013-11-06T13:14:13.910 回答