一个包含30亿次迭代的循环表现奇怪。

14
当我试图回答这个问题时,我遇到了一个奇怪的行为(与他的不同:他的是由于迭代次数太少,我的是由于迭代次数太多):
HTML:
<button id="go">it will be legend...</button>
<div id="output"></div>

JS:

var output = document.getElementById('output');
document.getElementById('go').onclick = function() {
    output.textContent += 'wait for it...';
    for (var i=0; i<3000000000; i++) {
        var unused = i; // don't really care
    }
    output.textContent += ' dary!';
};

由于其中有 30 亿次的迭代,循环需要几秒钟来执行。

一旦按钮被点击,我期望的是:

  1. 出现“等待......”
  2. 由于循环,进程会稍微冻结一下
  3. 出现“Dary!”

实际发生的情况:

  1. 由于循环,进程会稍微冻结一下
  2. “等待... Dary!”一起出现

任何想法为什么会发生这种行为?

请自行检查:fiddle


1
在所有浏览器中都是这样的行为吗?如果设置一个小的超时,它会正确地工作。 - Huangism
我看到了那个小提琴,真的很奇怪... 我以为“循环”是一个非常简单的过程! - The Dark Knight
1
此外,浏览器会优化对DOM/CSS的更改,以便仅在必要时执行它们(以避免多个连续的回流)。 - Felix Kling
1
我的猜测是textContent的设置是异步的,而当浏览器尝试显示文本时,3亿次循环会冻结浏览器,因此在完成之前您将看不到任何更新,但是在完成时文本会再次设置。这里是带有超时的相同fiddle http://jsfiddle.net/2MrD8/2/ - Huangism
5个回答

17
原因在于该函数作为整体是同步执行的。在您将输出设置为等待它...时,它进入了长时间运行的循环并独占线程。如果您将其余部分包装在timeout中,则第一个文本将正常显示。
var output = document.getElementById('output');
document.getElementById('go').onclick = function() {
    output.textContent += 'wait for it...';
    window.setTimeout(function() {
    for (var i=0; i<3000000000; i++) {
        var unused = i; // don't really care
    }
    output.textContent += ' dary!';
    }, 0);
};

请注意,处理过程中仍会冻结用户界面。
编辑:在Chrome浏览器中使用0作为延迟值可以正常工作,但是最新的Firefox和IE 10浏览器不行。将该值更改为10在这两种情况下都可以正常工作。

我在 Fiddle 中运行了这个程序,它仍然一起打印出“等待它...dary!” - Sid
@Sid 我在 JSFiddle 上测试了一下,使用 Chrome 浏览器时表现正常。JSFiddle。更新:我刚在 Firefox 中测试了一下,它确实显示在一起。如果你将 0 改为 10,它会按预期工作。看起来 Firefox 在某些情况下是同步执行的。 - Simon Belanger
你可能想要编辑这一行 output.textContent += ' dary!';}, 0); 我将其设置为100,现在它按预期正常工作。请查看此fiddle - Sid
@user568109:不完全正确。在此处同步运行的是浏览器。它通常将控制权传递给JS,然后等待脚本返回,然后再处理由其引起的视觉副作用。(我不确定这是否是硬性规定,但大多数浏览器都是如此做的)脚本所做的更改对其立即可见,但在浏览器重新获得控制并回到处理UI消息之前,用户看不到这些更改。 - cHao
请注意,如果将 setTimeout(function() { /* tie up the CPU for ages, then output */ }, 0); 替换为 setTimeout(function() { /* output immediately */ }, 15000);(或其他适当的超时值),则对用户更好。这将立即返回,因此第一条消息出现,并避免了繁忙等待,使浏览器看起来像被锁定了。 - cHao
显示剩余2条评论

10

Javascript几乎是单线程的。如果你在运行代码,页面将会无响应并且在你的代码完成前不会更新。(请注意这是具体实现而定,但目前所有浏览器都是如此做的。)


有没有办法强制页面按照所描述的消息进行更新? - cssyphus
1
按照其他答案中提到的方法插入延迟。 - Dark Falcon

2

Dark Falcon和Simon Belanger已提供了原因的解释;本篇文章讨论了另一种解决方案。但是,相对于30亿次迭代来说,这种解决方案绝对不适用,因为它速度太慢了。

根据这篇由用户Cocco发布在Stack Overflow的帖子,使用requestAnimationFramesetTimeout更有效。因此,以下是如何使用requestAnimationFrame:

jsFiddle示例

$(document).ready(function() {
    var W = window,
        D = W.document,
        i = 0,
        x = 0,
        output = D.getElementById('output');

    function b() {
        if (x == 0) {
            output.textContent = 'wait for it...';
            x++;
        }
        i++;
        if (i < 300) {
            //if (i > 20) output.textContent = i;
            requestAnimationFrame(b);
        } else {
            D.body.style.cursor = 'default';
            output.textContent += ' dary!';
        }
    }

    function start() {
        console.log(D)
        D.body.style.cursor = 'wait';
        b();
    }
    D.getElementById('go').onclick = start;
}); //END $(document).ready()

1
您的代码正在按照预期执行。问题在于,浏览器不会在javascript完成之前显示对文档的更改。超时可以通过将代码执行分为两个单独的事件来解决此问题。以下代码将显示您所期望的结果。
var output = document.getElementById('output');
document.getElementById('go').onclick = function() {
    console.log('wait for it...';)
    for (var i=0; i<3000000000; i++) {
        var unused = i; // don't really care
    }
    console.log(' dary!');
};

在使用超时解决方案时,您还需要小心,因为执行不再是同步的。

output = document.getElementById('output');
document.getElementById('go').onclick = function() {
    output.textContent += 'wait for it...';
    window.setTimeout(function() {
        for (var i = 0; i < 3000000000; i++) {
        var unused = i;
        // don't really care
        }
    output.textContent += ' dary!';
    }, 0);
    output.textContent += ' epic';
};

如果您运行此版本,您会注意到“epic”在“dary”之前。

0
我所能看到的唯一解释是浏览器在执行 JavaScript 后刷新视图?作为证明,以下代码的效果如预期:
var output = document.getElementById('output');
document.getElementById('go').onclick = function () {
    output.textContent += 'wait for it...';
    window.setTimeout(count, 100);
};

function count() {
    for (var i = 0; i < 3000000000; i++) {
        var unused = i; // don't really care
    }
    output.textContent += ' dary!';
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接