用JavaScript操作DOM时,经常有生成复杂HTML结构的需求。此时,通常不是用标准DOM接口(如createElement()
、setAttribute()
、append()
等)来语句式地生成,而是直接给innerHTML
或outerHTML
属性赋值HTML代码字符串,让DOM自动解析。显然,这样既方便,又容易阅读和理解。不过,这里面有一个不容易察觉的坑,那就是异步性问题。
经过实验验证,得到如下结论:
-
innerHTML
和innerText
的赋值是同步的; -
outerHTML
和outerText
的赋值是异步的!
所以,在使用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>
在浏览器中运行之,页面显示结果为:
控制台输出结果为:
可以看到,innerHTML
赋值后再读,得到的是新值,获取新后代也成功,这意味着在第二次读之前,DOM已经完成了变化。此即说明innerHTML
的写是同步的。
但是,outerHTML
赋值后再读,得到的仍然是旧值,获取新后代也失败,这意味着在第二次读时,DOM还没有发生变化。此即说明outerHTML
的写是异步的:做outerHTML = "……"
时,实际上就跟setTimeout(……, 0)
一样是开启了另一个线程,DOM的变化由这个新线程来做,原线程中立刻继续往后执行,此时DOM还未来得及变成新的,所以结果仍然是旧值。
innerText
和outerText
属性结果与此相同。