title: JS中操作DOM是"同步"還是"異步"? date: 2019-08-08 17:55:00 tags:
- JAVAScript categories: JavaScript
很多時(shí)候"不得已"使用js操作DOM,這個(gè)操作過(guò)程到底是"同步"的還是"異步"呢?
很多時(shí)候"不得已"使用js操作DOM,這個(gè)操作過(guò)程到底是"同步"的還是"異步"呢?
一、操作DOM的栗子
按理說(shuō),在js的執(zhí)行中,對(duì)于DOM的操作都是同步執(zhí)行的,
<body></body>
<script>
var body = document.querySelector('body');
console.log(`1`);
var cDiv = document.createElement('div');
console.log(cDiv)
console.log(`2`);
body.AppendChild(cDiv)
console.log(body);
</script>
以上結(jié)果目前和我們預(yù)想的結(jié)果是一致的,自上而下依次同步執(zhí)行,這里劃重點(diǎn),js引擎線程。
接下來(lái)做一點(diǎn)修改
<style>
.easy {
width: 200px;
height: 200px;
background: lightgoldenrodyellow;
}
.hard {
background: lightsalmon;
transition: 2s all;
}
</style>
<body></body>
<script>
var body = document.querySelector('body');
console.log(`1`);
var cDiv = document.createElement('div');
console.log(cDiv);
console.log(`2`);
body.appendChild(cDiv)
console.log(body);
cDiv.classList.add('easy')
console.log(`3`);
// ======================
for(var i = 0;i<3000000000;i++);
cDiv.classList.add('hard')
console.log(cDiv)
// ======================
</script>
既然是同步執(zhí)行,那我在添加第二個(gè)樣式hard之前阻塞一下,理論上在阻塞的情況下<div>應(yīng)該的背景色是淡黃色吧?不過(guò)跑一下完全不對(duì)勁啊,出來(lái)的很慢不說(shuō),竟然直接就橘色了。這里劃重點(diǎn),GUI渲染線程
二、捋一捋問(wèn)題
- 有阻塞,在阻塞時(shí)沒(méi)有顯示已有樣式,究竟是不是同步執(zhí)行的?
- console.log()的內(nèi)容并不是空,只是返回的很慢,看著像異步執(zhí)行?
- 過(guò)度樣式被忽略了,但背景色覆蓋執(zhí)行了,是什么原因?
三、依次解題
- js執(zhí)行順序不在這里細(xì)說(shuō),常見(jiàn)能夠改變執(zhí)行隊(duì)列的Promise、setTimeout、<script>標(biāo)簽等等都沒(méi)有在這里出現(xiàn),所以確認(rèn)是同步執(zhí)行無(wú)疑。
- 既然同步執(zhí)行為什么會(huì)有"異步"的效果,這里要說(shuō)到上文劃重點(diǎn)內(nèi)容: js引擎線程與GUI渲染線程。也就是說(shuō),js引擎線程與GUI渲染線程互斥,這是線程之間的"同步"造成的操作DOM時(shí)的"異步"效果。
- <div>的樣式為什么沒(méi)有生效呢?明明有一個(gè)過(guò)渡效果。原因是:瀏覽器的渲染時(shí)會(huì)執(zhí)行優(yōu)化策略,即將多個(gè)同一DOM下的樣式合并后渲染。
四、 總結(jié)
- js引擎線程與GUI渲染線程線程間的互斥,引起了對(duì)js操作DOM的"異步"問(wèn)題。
- GUI渲染線程在能夠執(zhí)行的情況下的優(yōu)化策略,渲染出的是最終得到的樣式結(jié)果。
具體的渲染線程的內(nèi)容,不在這次討論范圍之內(nèi)嘛。
雖然原因找到了,不過(guò)問(wèn)題好像還在。
五、 解決問(wèn)題
如果產(chǎn)品一定要從js創(chuàng)建出來(lái)的div擁有炫酷的特效(比如上面的過(guò)度樣式)。 呵呵呵呵
直接整理一下來(lái)自知乎各方大佬的解題思路, 這里不僅僅是過(guò)度樣式,類似問(wèn)題依然有效。
分析問(wèn)題:
- 過(guò)度效果是至少由A變B,也就是至少存有兩個(gè)不同狀態(tài);
- 由于上文所講的GUI渲染線程與js引擎的互斥會(huì)造成一種"同步"執(zhí)行的效果,所以創(chuàng)建<div>本身已經(jīng)被滯后了,缺少A。
- 又由于GUI渲染線程優(yōu)化策略,最后結(jié)果B將覆蓋可以覆蓋的所有。缺少了A(被覆蓋),之后被渲染出現(xiàn)在document內(nèi)。
- 本身已經(jīng)是B,且沒(méi)有A狀態(tài),過(guò)度效果無(wú)效。
解決方向就是使<div>擁有一個(gè)初始狀態(tài)A就搞定了。(提前將生成的DOM渲染到document上)
解決方法一:
cDiv.classList.add('easy')
// for(var i = 0;i<3000000000;i++);
setTimeout(() => {
cDiv.classList.add('hard')
}, 0)
思路: 利用setTimeout方法,改變執(zhí)行隊(duì)列。也就是手動(dòng)將js引擎滯后,使js引擎結(jié)束,被掛起的GUI渲染線程執(zhí)行,擁有了初始狀態(tài)A后,在執(zhí)行過(guò)度效果就OK了。
解決方法二(推薦):
cDiv.classList.add('easy')
cDiv.clientLeft; // 任一觸發(fā)頁(yè)面回流的方法皆可
cDiv.classList.add('hard')
思路:既然可以讓js引擎滯后,那也可以讓GUI渲染線程提前,用立即觸發(fā)回流的任意方法,使之前在渲染隊(duì)列中的狀態(tài)A生效。 相對(duì)優(yōu)點(diǎn)在于,同樣是觸發(fā)回流,方法二從代碼可讀性或操作性上都略勝一籌,優(yōu)秀團(tuán)隊(duì)有這種追求也是自然而然的。






