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