innerHTML不可信:不总是同步执行

Jon*_*nah 14 javascript

要查看操作中的问题,请参阅此jsbin.单击按钮会触发buttonHandler(),如下所示:

function buttonHandler() {
  var elm = document.getElementById("progress");
  elm.innerHTML = "thinking";
  longPrimeCalc();
}
Run Code Online (Sandbox Code Playgroud)

您可能希望此代码将div的文本更改为"思考",然后运行longPrimeCalc()一个需要几秒钟才能完成的算术函数.然而,这不是发生的事情.相反,"longPrimeCalc"首先完成,然后文本在运行完毕后更新为"思考",就好像两行代码的顺序相反.

看起来浏览器不会同步运行"innerHTML"代码,而是为它创建一个自己休闲执行的新线程.

我的问题:

  1. 导致这种行为的根本原因是什么?
  2. 如何让浏览器按照我期望的方式运行,即强制它在执行" longPrimeCalc()" 之前更新"innerHTML "?

我在最新版本的chrome中测试了这个.

Poi*_*nty 9

你的猜测是不正确的.该.innerHTML更新程序完成同步(和浏览器最肯定不创建一个新的线程).在您的代码完成之前,浏览器根本不需要更新窗口.如果您以某种方式询问DOM,需要更新视图,那么浏览器别无选择.

例如,在您设置之后innerHTML,添加以下行:

var sz = elm.clientHeight; // whoops that's not it; hold on ...
Run Code Online (Sandbox Code Playgroud)

编辑 - 我可能想办法欺骗浏览器,或者它可能是不可能的; 在单独的事件循环中启动长计算肯定会使它工作:

setTimeout(longPrimeCalc, 10); // not 0, at least not with Firefox!
Run Code Online (Sandbox Code Playgroud)

这里的一个很好的教训是浏览器努力不做页面布局的无意义的重新流动.如果您的代码已经在素数假期消失,然后又回来innerHTML 再次更新,那么浏览器可以节省一些毫无意义的工作.即使它没有绘制更新的布局,浏览器仍然必须弄清楚DOM发生了什么,以便在查询元素大小和位置等事项时提供一致的答案.


Cla*_*diu 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 =(";
    }
  });
}
Run Code Online (Sandbox Code Playgroud)