14

要查看实际问题,请参阅此 jsbin。单击按钮会触发buttonHandler(),如下所示:

function buttonHandler() {
  var elm = document.getElementById("progress");
  elm.innerHTML = "thinking";
  longPrimeCalc();
}

您会期望这段代码将 div 的文本更改为“thinking”,然后运行longPrimeCalc()一个需要几秒钟才能完成的算术函数。然而,事实并非如此。相反,“longPrimeCalc”先完成,然后在运行完成后将文本更新为“thinking”,就好像两行代码的顺序颠倒了一样。

看起来浏览器并没有同步运行“innerHTML”代码,而是为其创建了一个新线程,该线程在其空闲时执行。

我的问题:

  1. 导致这种行为的幕后情况是什么?
  2. 我怎样才能让浏览器按照我期望的方式运行,即强制它在执行“”之前更新“innerHTML” longPrimeCalc()

我在最新版本的 chrome 中对此进行了测试。

4

2 回答 2

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 发生了什么,以便在询问元素大小和位置等问题时提供一致的答案。

于 2013-05-27T15:49:02.067 回答
4
  1. 我认为它的工作方式是先完成当前运行的代码,然后完成所有页面更新。在这种情况下,调用longPrimeCalc会导致执行更多代码,并且只有在完成后才会更改页面更新。

  2. 要解决此问题,您必须终止当前运行的代码,然后在另一个上下文中开始计算。你可以用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 =(";
    }
  });
}
于 2013-05-27T16:15:38.030 回答