DOM編程 首先,需要明確JavaScript訪問DOM性能的開銷所在。在瀏覽器中。對於DOM和ECMAScript的支持一般是各自獨立實現的,因此通過ECMAScript訪問DOM自然而然就會產生較大的開銷,而且對於DOM的訪問越頻繁,性能的開銷越大。 儘量將運算放在ECMAScript中進行,避 ...
首先,需要明確JavaScript訪問DOM性能的開銷所在。在瀏覽器中。對於DOM和ECMAScript的支持一般是各自獨立實現的,因此通過ECMAScript訪問DOM自然而然就會產生較大的開銷,而且對於DOM的訪問越頻繁,性能的開銷越大。
-
儘量將運算放在ECMAScript中進行,避免操作DOM進行迭代更新
function addString = function (strArr) { strArr.forEach(str => { document.getElementById('id').innerHTML += str; // 讀取DOM的屬性,同時進行更新 }) } // 當需要進行DOM節點更新是可能會不自覺的使用以上的方法進行更新(至少我可能會這樣寫的) // 這種方法會帶來額外的性能開銷,因為在每一次迴圈遍歷中,都訪問了DOM中的innerHTML屬性,同時又對其進行了更新操作,而當字元串的數量較大時,這個開銷將會被放大 function addString2 = function (strArr) { let string = ''; strArr.forEach(str => string += str) document.getElementById('id').innerHTML += string; } // 第二種方式中,將字元串的拼接放在ECMAScript中完成,這樣對於DOM的操作就只有一次讀取和更新DOM的操作了,而這樣正是避免了對DOM的迭代更新。
-
HTMLCollection與NodeList
在看這本書之前,我其實一直以為HTML集合類型與NodeList類型是一樣的,而且原生獲取DOM元素使用的夜一直是document.getElement...方法。
HTML集合
-
包含DOM節點引用的偽數組對象,不能直接使用forEach方法進行遍歷
-
document.getElement...方法返回的是一個HTML集合對象
-
HTML集合會隨著界面中的DOM元素進行實時更新的,與文檔對象始終保留連接。
const divs = document.getElementByTagName('div'); // divs --> HTML集合 // 當刪除界面中的某一個div標簽時,divs中也會移除相對應的div元素 console.log(divs.length); // 當需要訪問divs.length屬性時,由於HTML集合"不知道界面中的元素是否有刪除或者增加",為了返回"實時"的divs當前長度,需要對文檔中的divs進行遍歷。即使文檔中的div元素沒有任何更新,這一步依舊無法避免,因此就帶來了額外的性能開銷
由於HTMLCollection的實時更新而帶來的一個意外的死迴圈
let divs = document.getElementsByTagName('div'); for (let i = 0; i < divs.length; i++) { document.body.appendChild(document.createElement('div')); } // 你寫這段的代碼的本意可能想使界面中的div加倍,但最後的結果卻是界面載入不出來 // 原因正是在於HTMLCollection的自動更新,當為body添加一個div標簽時,此時會更新divs.length,而每一次通過divs.length獲取到的是最新的(即更新之後的div的數目),因此迴圈永遠也不會終止。
-
儘量避免在迭代過程中讀取HTML集合的length屬性,因為每次都要查詢DOM以獲取當前的集合長度,在迴圈中可以將HTMLCollection的length屬性緩存避免其迭代更新
-
訪問HTMLCollection的速度比訪問數組慢得多,如有必要可以將HTMLCollection轉化為數組
NodeList
-
首先,NodeList是一個數組類型,可以直接使用forEach進行遍歷
-
不同於HTMLCollection,NodeList類型不會隨著界面DOM元素的更新而變化,因此訪問效率會高於HTMLCollection
-
document.querySelector(),document.querySelectorAll()方法返回的是NodeList對象
let divs = document.querySelectorAll('div'); for (let i = 0; i < divs.length; i++) { document.body.appendChild(document.createElement('div')); } // 由於NodeList不再進行實時更新因此,同樣的代碼不再產生死迴圈
-
當界面中需要用到大量組合查詢時,querySelector()與querySelectorAll()方法會比較簡單,可以支持CSS選擇器
-
-
重繪與重排
重排
-
發生時機:元素的幾何和位置屬性發生變化時,會觸發界面重排
重繪
-
界面發生了改變(包括元素的位置,幾何形狀,顏色等等屬性),瀏覽器就會重新繪製界面
-
防止在更新界面的過程中調用一下屬性:
-
offsetTop, offsetLeft, offsetWidth, offsetHeight
-
scrollTop, scrollLeft, scrollWidth, scrollHeight
-
clientTop, clientLeft, clientWidth, clientHeight
-
getComputedStyle(), currentStyle() ---> IE
上述屬性需要返回最新的佈局信息,因此會觸發立即更新,強制執行界面更新以返回正確的佈局信息
-
-
防止在更新界面信息時讀取界面信息,讀取操作將會觸發當前渲染隊列的更新
const div = document.getElementById('id'); div.style.backgroundColor = 'red'; div.style.backgroundColor = 'green'; div.style.backgroungColor = 'yellow'; console.log(div.style.backgroundColor); // 在此時讀取樣式值,由於javascript解析的速度極快,因此div的背景顏色在很短的時間內被修改了兩次,但界面的重繪只會發生一次,即由紅色變為黃色 div.style.backgroundColor = 'red'; div.style.backgroundColor = 'green'; console.log(div.style.backgroundColor); // 由於在此處JavaScript訪問了div的backgroundColor屬性,為了返回當前div的正確顏色,將會觸發div元素的重繪,即從紅色變為綠色的過程,增加了界面渲染的開銷 // 當然,在此處獲取backgroudColor屬性是沒有意義的,因為很快就會被覆蓋,此處僅僅為了說明這樣一個問題:不要在界面樣式改變的過程中訪問元素的屬性值,這樣將會觸發界面的重繪,增加開銷 div.style.backgroungColor = 'yellow'; console.log(div.style.backgroundColor);
-
最小化重繪和重排
-
合併多次對DOM的修改,一次性處理掉,在DOM修改的過程中,避免讀取元素的樣式,可以通過修改csscText屬性或者通過修改類名的方式來修改樣式實現
-
批量更新DOM
當需要對DOM進行多次修改時(大於兩次),此時可以採用一種通用的模式來更新DOM
-
使元素脫離文檔流 ---> 脫離時會觸發界面重排和重繪
-
對元素進行多重更新
-
將元素帶迴文檔流 ---> 帶迴文檔流時也需要進行重排和重繪
-
隱藏元素,應用修改,重新顯示
-
使用DocumentFragment
-
將元素拷貝到一個脫離文檔流的節點中,修改DOM,然後替換回去
hover偽類選擇器將會降低頁面的響應速度,不推薦大量使用
事件委托
-
事件委托是基於事件冒泡(事件由源對象向父級逐級傳遞)原理
-
綁定事件處理函數是需要代價的,因此需要儘量將大量的“<li></li>”上的事件委托給父元素,然後通過時間源對象的判斷對相應的DOM元素響應事件回調函數
-
-
-