outerHTML和outerText的赋值是异步的

用JavaScript操作DOM时,经常有生成复杂HTML结构的需求。此时,通常不是用标准DOM接口(如createElement()setAttribute()append()等)来语句式地生成,而是直接给innerHTMLouterHTML属性赋值HTML代码字符串,让DOM自动解析。显然,这样既方便,又容易阅读和理解。不过,这里面有一个不容易察觉的坑,那就是异步性问题

经过实验验证,得到如下结论:

  • innerHTMLinnerText的赋值是同步的;
  • outerHTMLouterText赋值是异步的!

所以,在使用outerHTML时要注意到这一点,避免想当然地认为做完了赋值就等于DOM已经变化好了。

如果outerHTML赋值之后的代码依赖于该DOM变化的结果,则不应该使用outerHTML,而应该改用innerHTML。对于innerHTML所属的元素,则可以用DOM接口方法来改变其状态。这些都是同步的,可以确信DOM已经发生了变化。

实验验证

实验代码如下:

<div id="inner-test">
    <p>inner</p>
</div>
<div id="outer-test">
    <p>outer</p>
</div>

<script>
    let innerTest = document.querySelector("#inner-test");
    console.group("inner测试:");
    console.log("赋值前读:", innerTest.innerHTML);
    innerTest.innerHTML = "<h2>NEW INNER</h2>"; // 赋值
    console.log("赋值后读:", innerTest.innerHTML);
    console.log("获取后代:", innerTest.querySelector("h2"));
    console.groupEnd();

    let outerTest = document.querySelector("#outer-test");
    console.group("outer测试:");
    console.log("赋值前读:", outerTest.outerHTML);
    outerTest.outerHTML = "<h2>NEW OUTER</h2>"; // 赋值
    console.log("赋值后读:", outerTest.outerHTML);
    console.log("获取后代:", outerTest.querySelector("h2"));
    console.groupEnd();
</script>

在浏览器中运行之,页面显示结果为:

outerHTML和outerText的赋值是异步的

控制台输出结果为:

outerHTML和outerText的赋值是异步的

可以看到,innerHTML赋值后再读,得到的是新值,获取新后代也成功,这意味着在第二次读之前,DOM已经完成了变化。此即说明innerHTML的写是同步的。

但是,outerHTML赋值后再读,得到的仍然是旧值,获取新后代也失败,这意味着在第二次读时,DOM还没有发生变化。此即说明outerHTML的写是异步的:做outerHTML = "……"时,实际上就跟setTimeout(……, 0)一样是开启了另一个线程,DOM的变化由这个新线程来做,原线程中立刻继续往后执行,此时DOM还未来得及变成新的,所以结果仍然是旧值。

innerTextouterText属性结果与此相同。

上一篇:Lab: DOM XSS in innerHTML sink using source:innerHTML使用源的接收器中的DOM XSSlocation.search


下一篇:js把空格换成br标签