【问题标题】:Long task in JavascriptJavascript中的长任务
【发布时间】:2010-05-22 21:28:45
【问题描述】:

为什么在following code 中我会一次看到整个页面?谢谢!

HTML:

<div></div>

CSS:

div {
    width: 100px;
    height: 300px;
    border: 1px solid black;
    text-align: center;
}

Javascript:

$(function() {
    for (var i=0; i<15; i++) {
        sleep(100);
        $("div").append("<span>--- " + i + " ---<br /></span>");
    }

    function sleep(milliseconds) {
        var start = new Date().getTime();
        for (var i = 0; i < 1e7; i++) {
            if ((new Date().getTime() - start) > milliseconds){
                break;
            }
        }
    }
});

【问题讨论】:

  • 你只在 JSFiddle 上测试过吗?我怀疑 JSFiddle 会阻止在脚本运行完成之前绘制结果,因为当我将 sleep(100) 更改为 sleep(1000) 时,加载需要更长的时间。
  • 哦,您可能应该使用while (true) 而不是for 循环。
  • @Austin:这不是 JSFiddle,这只是 Javascript 当前在浏览器上的工作方式。那个 sleep 函数并没有做他想做的事情。

标签: javascript jquery html


【解决方案1】:

因为 Web 浏览器上的 Javascript 是单线程的(尽管这种情况正在发生变化;见下文),并且几乎没有浏览器在(主)Javascript 线程繁忙时更新其 UI。您的所有sleep 函数所做的只是将所有内容阻止 100 毫秒,它不会让浏览器执行任何其他操作,例如更新其 UI(例如,不“屈服”)。

有几种方法可以做你想做的事情:

  1. 使用新的web workers 东西;但请注意,它是not widely-supported yet

  2. 使您的循环成为一个通过setTimeout 调用自身的函数。这样一来,您的代码就会让位于浏览器并让它更新其 UI。

这是一个简单示例,说明如何将上面的#2 应用到代码中:

$(function() {
    var i;

    doOne();

    function doOne() {
        $("div").append("<span>--- " + i + " ---<br /></span>");
        if (i++ < 15) {
            setTimeout(doOne, 0);   // <== Won't really be zero, browsers clamp it at ~10
        }
    }

});

如果您有很多循环迭代(例如,几百次而不是 15 次),那么在每次迭代中进行大量循环而不是在每次迭代中让步可能是值得的;产量需要一段可测量的时间(通常约为 10-15 毫秒)。

【讨论】:

  • 除非您在浏览器中实现 Web Workers:whatwg.org/specs/web-workers/current-work
  • @the_drow:嗯……那是我的方式#1。 :-)
  • 当迭代次数很高(比如 10000)时,这种递归会出现问题。 Firefox 3.6.3 说“递归太多”。你有什么解决方法吗?
  • @Misha:上面的例子中根本没有递归。对setTimeout 的调用立即返回,然后doOne 立即返回,展开堆栈。然后在短暂的间隔后,浏览器再次调用doOne,它会执行下一位。您是否在setTimeout 通话中将() 放在doOne 之后?他们需要 not 在那里,您正在引用该函数,而不是直接调用它。
  • @Misha:当前版本的 Firefox、Chrome、Safari 和 Opera 似乎支持 web worker,这只会让 IE(我知道最大的)被冷落。可能值得考虑使用它们,至少可以选择使用上述 IE 的后备。我敢打赌 IE9 会有网络工作者。
【解决方案2】:

由于 javascript 是单线程的,因此您需要将一些处理时间交给 UI,如下所示:

$(function() {
    function appendDiv(i) {
        $("div").append("<span>--- " + i + " ---<br /></span>");
        if(i < 14) 
            setTimeout(function() { appendDiv(i+1); }, 100);  
    }
    appendDiv(0);
});​

You can see a demo here

您也可以根据需要使用间隔,like this

$(function() {
    var i = 0;
    var interval = setInterval(function() {
        $("div").append("<span>--- " + i++ + " ---<br /></span>");
        if(i == 15) clearInterval(interval);
    }, 100);
});​

【讨论】:

  • 谢谢你的例子。 setInterval 不是我需要的。我在 Javascript 中有一个大约需要 10 秒的大型计算,我想在此计算期间进行一些更新以改善用户体验。正如我上面提到的,当迭代次数很高(比如 10000)时,使用 setTimeout 的递归会出现问题。你有什么别的想法吗?
  • @Misha - 你在做什么需要这么长时间? :) 它不能与页面一起发送,在服务器上预先计算吗?这将比 JavaScript 快得多。
  • 我绝对可以在服务器端完成这项长期任务,但这需要对我的程序进行重大更改。我想检查这是否可以在 Javascript 中完成。
  • @Misha - 我强烈建议你这样做,这是更好的用户体验。
  • 好的,我会考虑到这一点。谢谢!
猜你喜欢
  • 1970-01-01
  • 2011-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-14
  • 1970-01-01
  • 1970-01-01
  • 2013-07-08
相关资源
最近更新 更多