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

时间:2013-05-27 15:46:46

标签: javascript

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

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

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

浏览器似乎没有同步运行“innerHTML”代码,而是为它创建了一个新的线程,可以自行执行。

我的问题:

  1. 导致这种行为的根本原因是什么?
  2. 如何让浏览器按照我期望的方式运行,即强制它在执行“longPrimeCalc()”之前更新“innerHTML”?
  3. 我在最新版本的chrome中测试了这个。

2 个答案:

答案 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)

  1. 我认为它的工作方式是首先完成当前运行的代码,然后完成所有页面更新。在这种情况下,调用longPrimeCalc会导致执行更多代码,并且只有在完成后才会更改页面更新。

  2. 要解决此问题,您必须终止当前正在运行的代码,然后在另一个上下文中开始计算。您可以使用setTimeout执行此操作。我不确定除此之外是否还有其他办法。

  3. 这是显示行为的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 =(";
        }
      });
    }