要查看问题,请参阅this jsbin。单击按钮会触发buttonHandler()
,如下所示:
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking";
longPrimeCalc();
}
您可能希望此代码将div的文本更改为“思考”,然后运行longPrimeCalc()
,这是一个需要几秒钟才能完成的算术函数。然而,这不是发生的事情。相反,“longPrimeCalc”首先完成,然后文本在运行完毕后更新为“思考”,就像两行代码的顺序被颠倒一样。
浏览器似乎没有同步运行“innerHTML”代码,而是为它创建了一个新的线程,可以自行执行。
我的问题:
longPrimeCalc()
”之前更新“innerHTML”?我在最新版本的chrome中测试了这个。
答案 0 :(得分:9)
你的猜测是不正确的。 .innerHTML
更新确实同步完成(并且浏览器肯定不会创建新线程)。在代码完成之前,浏览器根本不需要更新窗口。如果您以某种方式询问DOM 需要要更新的视图,那么浏览器别无选择。
例如,在您设置innerHTML
之后,添加以下行:
var sz = elm.clientHeight; // whoops that's not it; hold on ...
编辑 - 我可能想办法欺骗浏览器,或者它可能是不可能的;确实,在单独的事件循环中启动长计算会使其工作:
setTimeout(longPrimeCalc, 10); // not 0, at least not with Firefox!
这里的一个好教训是,浏览器尽量不做无意义的页面布局重新流动。如果您的代码在素数假期结束后再返回并再次更新innerHTML
,则浏览器可以保存一些毫无意义的工作。即使它不是绘画更新的布局,浏览器仍然必须弄清楚DOM发生了什么,以便在询问元素大小和位置等事项时提供一致的答案。
答案 1 :(得分:4)
我认为它的工作方式是首先完成当前运行的代码,然后完成所有页面更新。在这种情况下,调用longPrimeCalc
会导致执行更多代码,并且只有在完成后才会更改页面更新。
要解决此问题,您必须终止当前正在运行的代码,然后在另一个上下文中开始计算。您可以使用setTimeout
执行此操作。我不确定除此之外是否还有其他办法。
这是显示行为的jsfiddle。您不必将回调传递给longPrimeCalc
,您只需要创建另一个函数,它可以使用返回值执行您想要的操作。基本上,您希望将计算推迟到执行的另一个“线程”。以这种方式编写代码可以明显地显示您正在做的事情(再次更新以使其更好):
function defer(f, callback) {
var proc = function() {
result = f();
if (callback) {
callback(result);
}
}
setTimeout(proc, 50);
}
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking...";
defer(longPrimeCalc, function (isPrime) {
if (isPrime) {
elm.innerHTML = "It was a prime!";
}
else {
elm.innerHTML = "It was not a prime =(";
}
});
}