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