TL; DR:使用CPS:http://jsfiddle.net/christophercurrie/DHqeR/
与在accepted answer代码中的问题(如)是它创建了一个超时事件队列,在三重循环已经退出之前不会触发。您实际上并未实时看到进度栏更新,但看到了一个延迟报告,说明在内部关闭中捕获变量时的值。
我希望你的'递归'解决方案看起来有点像使用continuation-passing style来确保你的循环不会继续,直到你通过setTimeout取得控制权。你可能不知道你在使用CPS,但是如果你使用setTimeout来实现一个循环,你可能会非常接近它。
我已经详细说明了这种方法供将来参考,因为知道这些方法很有用,并且结果演示的性能比所提供的演示效果要好。使用三重嵌套循环看起来有些复杂,所以对于您的用例来说可能是过度的,但可以在其他应用程序中使用。
(function($){
function run() {
var x = 100,
y = 100,
z = 10,
count = 0;
/*
This helper function implements a for loop using CPS. 'c' is
the continuation that the loop runs after completion. Each
'body' function must take a continuation parameter that it
runs after doing its work; failure to run the continuation
will prevent the loop from completing.
*/
function foreach(init, max, body, c) {
doLoop(init);
function doLoop(i) {
if (i < max) {
body(function(){doLoop(i+1);});
}
else {
c();
}
}
}
/*
Note that each loop body has is own continuation parameter (named 'cx',
'cy', and 'cz', for clarity). Each loop passes the continuation of the
outer loop as the termination continuation for the inner loop.
*/
foreach(0, x, function(cx) {
foreach(0, y, function(cy) {
foreach(0, z, function(cz) {
count += 1;
$('#progressbar').reportprogress((100*(count))/(x*y*z));
if (count * 100 % (x*y*z) === 0) {
/*
This is where the magic happens. It yields
control to the javascript event loop, which calls
the "next step of the foreach" continuation after
allowing UI updates. This is only done every 100
iterations because setTimeout can actually take a lot
longer than the specified 1 ms. Tune the iterations
for your specific use case.
*/
setTimeout(cz, 1);
} else {
cz();
}
}, cy);
}, cx);
}, function() {});
}
$('#start').click(run);
})(jQuery);
你可以在jsFiddle上看到这个版本更新很顺利。
进度条更新似乎不是非常流畅。它会跳到百分之几(2或3%),然后停下来,然后突然达到100%。任何想法为什么这样做? – Josiah
我确实需要使用setTimeout,但不是使用闭包,而是使主计算函数递归。我开始意识到setTimeouts不会暂停处理其余的代码,但是您可以使用递归函数似乎强制setTimeouts运行。 – Josiah