php 频繁dom和 文件,性能优化之为什么不要频繁操作DOM
性能優(yōu)化:為什么不要頻繁操作DOM
@[toc]
性能優(yōu)化的時候,我們常說“不要頻繁操作DOM”,但是“DOM 為什么這么慢”以及“如何使 DOM 變快”呢。
DOM 為什么這么慢,因為,DOM和JS的跨界交流
把 DOM 和 JavaScript 各自想象成一個島嶼,它們之間用收費橋梁連接。——《高性能 JavaScript》
JS 是很快的,在 JS 中修改 DOM 對象也是很快的。在JS的世界里,一切是簡單的、迅速的。但 DOM 操作并非 JS 一個人的獨舞,而是兩個模塊之間的協(xié)作。
JS 引擎和渲染引擎(瀏覽器內(nèi)核)是獨立實現(xiàn)的。當(dāng)我們用 JS 去操作 DOM 時,本質(zhì)上是 JS 引擎和渲染引擎之間進行了“跨界交流”。
“跨界交流”要收費——這個開銷本身就是不可忽略的。我們每操作一次 DOM(不管是為了修改還是僅僅為了訪問其值),都要跨界一次。跨界的次數(shù)一多,就會產(chǎn)生比較明顯的性能問題。因此“減少 DOM 操作”的建議,并非空穴來風(fēng)。
對 DOM 的修改引發(fā)樣式的更迭
很多時候,我們對 DOM 的操作都不會局限于訪問,而是為了修改它。當(dāng)我們對 DOM 的修改會引發(fā)它外觀(樣式)上的改變時,就會觸發(fā)回流或重繪。
這個過程本質(zhì)上還是因為我們對 DOM 的修改觸發(fā)了渲染樹(Render Tree)的變化所導(dǎo)致的:
回流:當(dāng)我們對 DOM 的修改引發(fā)了 DOM 幾何尺寸變化(比如修改元素的寬、高或隱藏元素等)時,瀏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然后再將計算的結(jié)果繪制出來。這個過程就是回流(也叫重排)。
重繪:當(dāng)我們對 DOM 的修改導(dǎo)致了樣式的變化、卻并未影響其幾何屬性(比如修改了顏色或背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪制新的樣式(跳過了上圖所示的回流環(huán)節(jié))。這個過程叫做重繪。
由此我們可以看出,重繪不一定導(dǎo)致回流,回流一定會導(dǎo)致重繪。硬要比較的話,回流比重繪做的事情更多,帶來的開銷也更大。但這兩個說到底都是吃性能的,所以都不是什么善茬。我們在開發(fā)中,要從代碼層面出發(fā),盡可能把回流和重繪的次數(shù)最小化。
如何使DOM變快
知道了 DOM 慢的原因,我們就可以對癥下藥了。
減少 DOM 操作:少“跨界交流”
例子,HTML 內(nèi)容如下:
DOM操作測試此時我有一個假需求——我想往 container 元素里寫 10000 句一樣的話。如果我這么做:
for(var count=0;count<10000;count++){
document.getElementById('container').innerHTML+='我是一個小測試'
}
這段代碼有兩個明顯的可優(yōu)化點。
第一點,跨界交流太多了。我們每一次循環(huán)都調(diào)用 DOM 接口重新獲取了一次 container 元素,相當(dāng)于每次循環(huán)都進行一次“跨界交流”。前后交了 10000 次,但其中 9999 次都可以用緩存變量的方式節(jié)省下來:
// 只獲取一次container
let container = document.getElementById('container')
for(let count=0;count<10000;count++){
container.innerHTML += '我是一個小測試'
}
第二點,不必要的 DOM 更改太多了。我們的 10000 次循環(huán)里,修改了 10000 次 DOM 樹。我們前面說過,對 DOM 的修改會引發(fā)渲染樹的改變、進而去走一個(可能的)回流或重繪的過程,而這個過程的開銷是很“貴”的。這么貴的操作,我們竟然重復(fù)執(zhí)行了 N 多次!其實我們可以通過就事論事的方式節(jié)省下來不必要的渲染:
let container = document.getElementById('container')
let content = ''
for(let count=0;count<10000;count++){
// 先對內(nèi)容進行操作
content += '我是一個小測試'
}
// 內(nèi)容處理好了,最后再觸發(fā)DOM的更改
container.innerHTML = content
JS 層面的事情,JS 自己去處理,處理好了,再來找 DOM 打報告。
事實上,考慮JS 的運行速度,比 DOM 快得多這個特性。我們減少 DOM 操作的核心思路,就是讓 JS 去給 DOM 分壓。
這個思路,在 DOM Fragment 中體現(xiàn)得淋漓盡致。
DocumentFragment 接口表示一個沒有父級文件的最小文檔對象。它被當(dāng)做一個輕量版的 Document 使用,用于存儲已排好版的或尚未打理好格式的XML片段。因為 DocumentFragment 不是真實 DOM 樹的一部分,它的變化不會引起 DOM 樹的重新渲染的操作(reflow),且不會導(dǎo)致性能等問題。
在我們上面的例子里,字符串變量 content 就扮演著一個 DOM Fragment 的角色。其實無論字符串變量也好,DOM Fragment 也罷,它們本質(zhì)上都作為脫離了真實 DOM 樹的容器出現(xiàn),用于緩存批量化的 DOM 操作。
前面我們直接用 innerHTML 去拼接目標(biāo)內(nèi)容,這樣做固然有用,但卻不夠優(yōu)雅。相比之下,DOM Fragment 可以幫助我們用更加結(jié)構(gòu)化的方式去達成同樣的目的,從而在維持性能的同時,保住我們代碼的可拓展和可維護性。我們現(xiàn)在用 DOM Fragment 來改寫上面的例子:
let container = document.getElementById('container')
// 創(chuàng)建一個DOM Fragment對象作為容器
let content = document.createDocumentFragment()
for(let count=0;count<10000;count++){
// span此時可以通過DOM API去創(chuàng)建
let oSpan = document.createElement("span")
oSpan.innerHTML = '我是一個小測試'
// 像操作真實DOM一樣操作DOM Fragment對象
content.appendChild(oSpan)
}
// 內(nèi)容處理好了,最后再觸發(fā)真實DOM的更改
container.appendChild(content)
我們運行這段代碼,可以得到與前面兩種寫法相同的運行結(jié)果。
可以看出,DOM Fragment 對象允許我們像操作真實 DOM 一樣去調(diào)用各種各樣的 DOM API,我們的代碼質(zhì)量因此得到了保證。
總結(jié)
以上是生活随笔為你收集整理的php 频繁dom和 文件,性能优化之为什么不要频繁操作DOM的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php日期时间代码,PHP日期计算
- 下一篇: php 模拟 cas,PHP discu