淺談DOM的操作以及性能優(yōu)化問題-重繪重排
寫在前面:
大家都知道DOM的操作很昂貴?!?/p>
然后貴在什么地方呢?
一、訪問DOM元素
二、修改DOM引起的重繪重排
一、訪問DOM
像書上的比喻:把DOM和JavaScript(這里指ECMScript)各自想象為一個(gè)島嶼,它們之間用收費(fèi)橋梁連接,ECMAScript每次訪問DOM,都要途徑這座橋,并交納“過橋費(fèi)”,訪問DOM的次數(shù)越多,費(fèi)用也就越高。因此,推薦的做法是盡量減少過橋的次數(shù),努力待在ECMAScript島上。我們不可能不用DOM的接口,那么,怎樣才能提高程序的效率?
既然無法避免,那就減少訪問。(width、offsetTop、left。。。能少就少,可以緩存起來的,就緩存)
// code1錯(cuò)誤 console.time(1); for(var i = 0; i < times; i++) { document.getElementById('div1').innerHTML += 'a'; } console.timeEnd(1); // code2正確 console.time(2); var str = ''; for(var i = 0; i < times; i++) { str += 'a'; } document.getElementById('div2').innerHTML = str; console.timeEnd(2); ////////////////////////
html集合&遍歷DOM
html集合類似數(shù)組,但是跟數(shù)組還是不一樣的。如: document.getElementsByTagName('a') 返回的html集合。這個(gè)集合是實(shí)時(shí)更新的,即后面代碼修改了DOM,會(huì)反映在這個(gè)html集合里面??蓢L試代碼。
<body> <ul id='fruit'> <li> apple </li> <li> orange </li> <li> banana </li> </ul> </body> <script type="text/javascript"> var lis = document.getElementsByTagName('li'); var peach = document.createElement('li'); peach.innerHTML = 'peach'; document.getElementById('fruit').appendChild(peach); console.log(lis.length); // 4 </script>
正因?yàn)檫@個(gè)原因:html集合,讀取 length 屬性比數(shù)組消耗大多了。
要解決這個(gè)問題并不難,在遍歷DOM集合的時(shí)候,緩存length就好了。不要每次使用就獲取,主要體現(xiàn)在for循環(huán)中(你應(yīng)該知道,for循環(huán)中,每一次都會(huì)執(zhí)行判讀語句,讀取length)
console.time(0); var lis0 = document.getElementsByTagName('li'); var str0 = ''; for(var i = 0; i < lis0.length; i++) { str0 += lis0[i].innerHTML; } console.timeEnd(0); console.time(1); var lis1 = document.getElementsByTagName('li'); var str1 = ''; for(var i = 0, len = lis1.length; i < len; i++) { str1 += lis1[i].innerHTML; } console.timeEnd(1);
二、重繪重排
1.什么是重繪重排?
瀏覽器下載完頁面中的所有組件——HTML標(biāo)記、JavaScript、CSS、圖片之后會(huì)解析生成兩個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu)——DOM樹和渲染樹。
在文檔初次加載時(shí),瀏覽器引擎通過解析 html文檔 構(gòu)建一棵DOM樹,之后根據(jù)DOM元素的幾何屬性構(gòu)建一棵用于展示渲染的渲染樹。渲染樹中的節(jié)點(diǎn)被稱為“幀”或“盒",符合CSS模型的定義,可理解為(包括理解頁面元素為一個(gè)具有大小,填充,邊距,邊框和位置的盒子)。由于隱藏元素不需要顯示,渲染樹中并不包含DOM樹中隱藏的元素(知道這點(diǎn)有用)。 當(dāng)渲染樹構(gòu)建完成,瀏覽器把每一個(gè)元素放到正確的位置上,然后再根據(jù)每一個(gè)元素的其他樣式,繪制頁面。
由于瀏覽器的流布局,對渲染樹的計(jì)算通常只需要遍歷一次就可以完成。但table及其內(nèi)部元素除外,它可能需要多次計(jì)算才能確定好其在渲染樹中節(jié)點(diǎn)的屬性,通常要花3倍于同等元素的時(shí)間。這也是為什么我們要避免使用table做布局的一個(gè)原因。
重繪:是一個(gè)元素外觀的改變所觸發(fā)的瀏覽器行為,例如改變visibility、outline、背景色等屬性(上面說到的其他屬性)。瀏覽器會(huì)根據(jù)元素的新屬性重新繪制,使元素呈現(xiàn)新的外觀。重繪不會(huì)帶來重新布局,并不一定伴隨重排。
重排:當(dāng)DOM的變化影響了元素的幾何屬性(寬或高),瀏覽器需要重新計(jì)算元素的幾何屬性,同樣其他元素的幾何屬性和位置也會(huì)因此受到影響。瀏覽器會(huì)使渲染樹中受到影響的部分失效,并重新構(gòu)造渲染樹。這個(gè)過程稱為重排。重排一定伴隨著重繪。
2. 觸發(fā)重排的操作:
2.1 修改DOM元素幾何屬性:
修改元素大小,位置,內(nèi)容(一般只有重繪,但是內(nèi)容可能導(dǎo)致元素大小變化)
2.2 DOM樹結(jié)構(gòu)發(fā)生變化
當(dāng)DOM樹的結(jié)構(gòu)變化時(shí),例如節(jié)點(diǎn)的增減、移動(dòng)等,也會(huì)觸發(fā)重排。瀏覽器引擎布局的過程,類似于樹的前序遍歷,是一個(gè)從上到下從左到右的過程。 通常在這個(gè)過程中,當(dāng)前元素不會(huì)再影響其前面已經(jīng)遍歷過的元素。所以,如果在body最前面插入一個(gè)元素,會(huì)導(dǎo)致整個(gè)文檔的重新渲染,而在其后插入一個(gè)元 素,則不會(huì)影響到前面的元素。
2.4 改變?yōu)g覽器大小
3.渲染樹變化的排隊(duì)和刷新
思考下面代碼:
var ele = document.getElementById('myDiv'); ele.style.borderLeft = '1px'; ele.style.borderRight = '2px'; // var _top = ele.offsetTop; //刷新隊(duì)列 ele.style.padding = '5px';
三行代碼,三次修改元素的幾何屬性,瀏覽器應(yīng)該發(fā)生三次重排重繪。
但是瀏覽器并不會(huì)這么笨,它也是有做優(yōu)化的。它會(huì)把三次修改“保存”起來(大多數(shù)瀏覽器通過隊(duì)列化修改并批量執(zhí)行來優(yōu)化重排過程,也有設(shè)置時(shí)間片段的),一次完成!
然而,如果你在三行代碼中,以下獲取DOM布局信息。(為了返回最新的布局信息,將立即執(zhí)行渲染樹變化隊(duì)列的更新)
如上面被注釋的第4行,如果取消注釋會(huì)導(dǎo)致(2+3)、(5)兩次重排;
獲取關(guān)于DOM布局信息的屬性:
offsetTop, offsetLeft, offsetWidth, offsetHeight scrollTop, scrollLeft, scrollWidth, scrollHeight clientTop, clientLeft, clientWidth, clientHeight getComputedStyle() (currentStyle in IE)
4 應(yīng)對方法:盡量減少重繪次數(shù)、減少重排次數(shù)、縮小重排的影響范圍。
4.1 合并多次操作,如上面的操作
ele.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
4.2 將需要多次重排的元素,position屬性設(shè)為absolute或fixed,這樣此元素就脫離了文檔流,它的變化不會(huì)影響到其他元素。例如有動(dòng)畫效果的元素就最好設(shè)置為絕對定位。
4.3 由于display屬性為none的元素不在渲染樹中,對隱藏的元素操作不會(huì)引發(fā)其他元素的重排。如果要對一個(gè)元素進(jìn)行復(fù)雜的操作時(shí),可以先隱藏它,操作完成后再顯示。這樣只在隱藏和顯示時(shí)觸發(fā)2次重排。但是這可能導(dǎo)致瀏覽器的閃爍。
4.4 在內(nèi)存中多次操作節(jié)點(diǎn),完成后再添加到文檔中去(可使用fragment元素)。例如要異步獲取表格數(shù)據(jù),渲染到頁面??梢韵热〉脭?shù)據(jù)后在內(nèi)存中構(gòu)建整個(gè)表格的html片段,再一次性添加到文檔中去,而不是循環(huán)添加每一行。
var fragment = document.createDocumentFragment(); // 未使用的虛擬節(jié)點(diǎn),appendChild(fragment) //append的是里面的子元素 var li = document.createElement('li'); li.innerHTML = 'apple'; fragment.appendChild(li); var li = document.createElement('li'); li.innerHTML = 'watermelon'; fragment.appendChild(li); document.getElementById('fruit').appendChild(fragment);
以上就是小編為大家?guī)淼臏\談DOM的操作以及性能優(yōu)化問題-重繪重排全部內(nèi)容了,希望大家多多支持腳本之家~
相關(guān)文章
原生JS封裝ajax 傳json,str,excel文件上傳提交表單(推薦)
這篇文章主要介紹了原生JS封裝ajax 傳json,str,excel文件上傳提交表單(推薦)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06動(dòng)態(tài)加載外部javascript文件的函數(shù)代碼分享
動(dòng)態(tài)加載外部javascript文件的函數(shù)代碼分享,做個(gè)記錄備忘,方便查找。2011-07-07移動(dòng)端(微信等使用vConsole調(diào)試console的方法
這篇文章主要介紹了移動(dòng)端(微信等使用vConsole調(diào)試console的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03ES6中Promise的使用方法實(shí)例總結(jié)
這篇文章主要介紹了ES6中Promise的使用方法,結(jié)合實(shí)例形式總結(jié)分析了Promise對象中的各種常用方法及基本使用技巧,需要的朋友可以參考下2020-02-02