【问题标题】:A 3,000,000,000 iterations loop behaves weirdly3,000,000,000 次迭代循环的行为很奇怪
【发布时间】:2013-08-20 16:05:38
【问题描述】:

在尝试回答 this question 时,我遇到了一个奇怪的行为(这不一样:他的迭代太少,我的迭代太多):

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!';
};

循环需要几秒钟的时间来执行,因为它有 3,000,000,000 次迭代。

单击按钮后,我的预期是:

  1. wait for it... 出现
  2. 由于循环,进程有点冻结
  3. dary! 出现

实际发生了什么:

  1. 由于循环,进程有点冻结
  2. wait for it... dary!一起出现

知道为什么会有这样的行为吗?

自行查看:fiddle

【问题讨论】:

  • 它在所有浏览器中的行为都是这样吗?如果您设置一个小的超时时间,则可以正常工作
  • 我看到了小提琴,这真的很奇怪......我认为looping 是一个普通的过程!
  • 浏览器还优化了对 DOM/CSS 的更改,以便仅在必要时执行它们(以避免多次连续重排)。
  • 我的猜测是设置 textContent 是异步的,当浏览器尝试显示文本时,30 亿循环正在冻结浏览器,因此在完成之前您不会看到任何更新,但是通过完成的时间再次设置文本。这是与超时相同的小提琴jsfiddle.net/2MrD8/2

标签: javascript loops execution-time


【解决方案1】:

原因是函数作为一个整体是同步执行的。当您将输出设置为wait for it... 时,它会进入长时间运行的循环并占用线程。如果您将其余部分包装在 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);
};

请注意,它在处理时仍会冻结 UI。

编辑:在 Chrome 中使用 0 作为延迟值有效,但在最新的 Firefox 和 IE 10 中无效。将值更改为 10 在这两种情况下都有效。

【讨论】:

  • 我在小提琴中运行了这个,它仍在一起打印wait for it...dary!
  • @Sid 我在小提琴中对其进行了测试并具有正确的行为(使用 chrome)。 JSFiddle。编辑:刚刚在 Firefox 中进行了测试,它确实一起显示了。如果您将0 更改为10,它将按预期工作。看起来 Firefox 在某些情况下是同步执行的。
  • 您可能想要编辑这一行 output.textContent += ' dary!'; }, 0); 我将它设置为 100,现在它按预期正常工作。检查这个fiddle
  • @Sid 是的,我刚刚测试过。虽然它在 Firefox 中的值为 10。
  • 如果它首先显示wait for it...而不是一起显示,那不是同步的吗?问题中发生的事情对我来说看起来是异步的,即在循环完成之前不显示wait for it...,然后与dary!一起显示。
【解决方案2】:

Javascript 几乎是单线程的。如果您正在运行代码,则页面是无响应的,并且在您的代码完成之前不会更新。 (请注意,这是特定于实现的,但现在所有浏览器都是这样做的。)

【讨论】:

  • 有什么方法可以强制按照描述的消息更新页面吗?
  • 插入延迟,如其他答案中所述。
【解决方案3】:

Dark Falcon 和 Simon Belanger 提供了原因解释;这篇文章讨论了一个不同的解决方案。但是,这种解决方案绝对不适合 30 亿次的迭代循环,因为相比之下它太慢了。

根据this SO post by user Cocco,为此目的使用setTimeout 不如requestAnimationFrame 最佳。所以,这里是如何使用requestAnimationFrame:

jsFiddle example

$(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()

注意:为了表示同意/赞赏,请在上面的帖子中投票Cocco's answer 而不是这个。谢谢。

【讨论】:

    【解决方案4】:

    您的代码正在按预期执行。问题是浏览器不会显示您对文档的更改,直到 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”之前。

    【讨论】:

      【解决方案5】:

      我看到的唯一解释是浏览器在执行 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!';
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-10
        • 2016-02-16
        • 2016-04-22
        相关资源
        最近更新 更多