元素的樣式爲什麼頻頻被改?前端
DOM 的屬性爲什麼離奇失蹤??數組
消失的 DOM 到底是何人所爲???瀏覽器
出現的陌生 DOM 到底是人是鬼????微信
這一切的背後是人性的扭曲仍是道德的淪喪?????異步
是無心寫的 Bug 仍是有人故意而爲之??????函數
讓咱們跟隨鏡頭,探尋變幻無窮的 DOM。工具
最近在作項目的時候,遇到一個問題:組件化
就是一個好久之前寫的頁面,裏面的代碼很亂。而個人任務是將頁面的高度與屏幕適配。在部分頁面存在着幾個 <iframe>
,我須要調整樣式使其高度與屏幕適配,可是不管我怎麼調整,總會有一個 JavaScript 在不停地修改 <iframe>
的高度,使得它的高度超出屏幕而出現兩個滾動條。性能
因爲代碼很亂,因此很難找到到底是哪一個代碼在搗亂。spa
在 Chrome 中開發前端是很是開心的一件事情,由於瀏覽器提供了很是多的調試工具。而我常常用的一個就是:斷點調試。
Chrome 提供了一個 DOM 監視的功能,當 DOM 發生變化的時候,自動暫停,這樣就能很快定位是誰修改了 <iframe>
的高度了!
在 Elements 頁面,選中指定的 DOM 節點,點擊最前面的「...」符號(或者使用右鍵點擊),在彈出的菜單中選擇「Break On」便可,能夠選擇多個。
只要啓用其中一個,在具體事件發生的時候,Chrome 就會自動中斷到當前執行的腳本代碼處。
因爲 JavaScript 修改樣式無非就是增長/刪除 class,或是修改 style 屬性,因此,使用 Attribute Modifications
便可找到具體是哪一個腳本在修改 DOM 了。
在作前端開發的時候,常常會想要監聽 DOM 節點的變化。當 DOM 變化的時候,觸發一系列事件。
通常來講,使用輪詢的方式能夠很是簡單地解決這個問題,就是使用 setInterval
來不斷地檢查 DOM 是否發生了變化。這種方式簡單粗暴,可是會遇到兩個問題:時間間隔設置過長,DOM 變化響應不夠及時;時間間隔設置太短,不只浪費 CPU,並且可能出現卡頓。
固然,也可使用 requestAnimationFrame
來作,原理其實和 setInterval
同樣,只不過在必定程度上能夠獲得 DOM 變化實時響應,可是依舊是致使 CPU 運行時間片的浪費。
有沒有更好的辦法呢?
在舊版 DOM Events 標準中,有一個 Mutation events
,能夠用來監聽 DOM 的變化,在 DOM 變化的時候觸發事件。
在 DOM3 中定義了 9 種 Mutation 事件:DOMAttrModified
、DOMAttributeNameChanged
、DOMCharacterDataModified
、DOMElementNameChanged
、DOMNodeInserted
、DOMNodeInsertedIntoDocument
、DOMNodeRemoved
、DOMNodeRemovedFromDocument
、DOMSubtreeModified
。
這 9 種事件能夠直接經過 element.addEventListener
添加到 DOM 元素上。
可是,Mutation 事件已經被反對使用!而且從 Web 標準事件中刪除了!
因爲性能問題,Mutation 事件會致使 DOM 修改的性能下降 1.5~7 倍,而且不能經過移除事件來恢復性能。
而且這個事件在各個瀏覽器上的實現也存在差別。
因此,DOM4 開始,推薦使用 Mutation Observers
來代替 Mutation events
。
Mutation Observer API 能夠用來監視 DOM 的變化,包括屬性的變化、節點的增減、內容的變化等。
爲何 Mutation Observers
要比 Mutation Events
好?
因爲 Mutation Events
是監視到 DOM 發生變化時產生的事件,它會在任何一個 DOM 發生變化的時候馬上被觸發。而且,因爲事件是同步進行的,因此若是 DOM 的變化較多,就會產生大量的事件回調,致使嚴重的性能問題。
而 Mutation Observers
雖然和 Mutation Events
很像,可是 Mutation Observers
不是事件,它是異步觸發的,而且不是每次 DOM 變更都會觸發,而是會等待屢次 DOM 變更完成後一次性觸發,使用一個數組來記錄 DOM 變更的步驟。這樣一來,即便是頻繁的 DOM 操做,對性能的影響也不會有多明顯。
舉個例子,我如今須要將一篇包含 1000 個段落的文章顯示到頁面上,也就是要往頁面中插入 1000 個 <p></p>
。
若是使用 Mutation Events
的話,這時就會產生 1000 個 DOMNodeInserted
事件;而若是使用 Mutation Observers
就不同了,它只會觸發一次,獲得一個數組,包含了 1000 個插入節點的信息。
MutationObserver
是一個構造函數,可使用 new
來建立一個 MutationObserver
的實例。這個構造函數接受一個回調函數做爲參數,也就是每次 Mutation Observers
觸發時調用的函數,函數接受兩個參數,第一個參數是 MutationRecord
數組,用於存儲 DOM 的變化記錄,第二個參數是 MutationObserver
實例自己。
MutationObserver
的實例有 3 個成員方法:observe
、disconnect
、takeRecords
。
observe
用於註冊監聽器,接受兩個參數,第一個參數是要監聽的節點,第二個參數是監聽的配置。
監聽配置是一個對象,能夠有 childList
、attributes
、characterData
、subtree
、attributeOldValue
、characterDataOldValue
、attributeFilter
,要監聽哪一種變化,只須要將對應的屬性設置爲 true
便可,其中 childList
、attributes
、characterData
三者必須至少出現一個。
屬性 | 數據類型 | 描述 |
---|---|---|
childList | boolean | 觀察目標增長或移除了子節點 |
attributes | boolean | 觀察目標增長、刪除或修改了某個屬性 |
characterData | boolean | (目標爲 characterData 節點時有效,包括文本節點、註釋節點、處理指令節點等)文本內容發生了變化 |
subtree | boolean | 不只監視 ovserve 第一個參數指定的觀察目標,同時監視全部的下級節點 |
attributeOldValue | boolean | 在監視 attributes 的時候,屬性發生變化後是否要記錄變化前的內容 |
characterDataOldValue | boolean | 在監視 characterData 的時候,文本內容發生變化後是否要記錄變化前的內容 |
attributeFilter | Array<string> | 一個屬性名數組,能夠用於過濾 attributes 的變化 |
註冊成功後,構造函數裏提供的回調函數將會被調用,第一個參數就獲得了變化數組。變化對象的結構包含如下屬性:
屬性 | 數據類型 | 描述 |
---|---|---|
type | String | 變化類型,對應監聽配置對象中的 childList 、attributes 、characterData |
target | Node | 變化的目標節點,若是 type 是 attributes 或 characterData ,則 target 爲變化節點,不然爲變化節點的父節點 |
addedNodes | NodeList | 被添加的節點列表(可能爲 null ) |
removedNodes | NodeList | 被刪除的節點列表(可能爲 null ) |
previousSibling | Node | 被添加或被刪除的節點的前一個兄弟節點(可能爲 null ) |
nextSibling | Node | 被添加或被刪除的節點的後一個兄弟節點(可能爲 null ) |
attributeName | String | 變化的屬性名稱(可能爲 null ) |
attributeNamespace | String | 變化的屬性所在的 XML 命名空間(可能爲 null ) |
oldValue | String | 若是 type 是 attributes 或 characterData ,則 oldValue 爲變化前的值,不然爲 null |
disconnect
用於中止監聽。
takeRecords
用於清空並返回當前 MutationObserver
記錄的 DOM 變化步驟。
能夠看到,兼容性仍是很是好的,能夠放心使用。
MutationObserver
提供了比 Mutation Events
更高效、更靈活的 DOM 監視方案,能夠根據本身的須要自定義監視對象,在組件化項目中能夠發揮更大的價值——不須要組件內部提供接口,就能夠收到組件內容變化的通知。
可是,MutationObserver
雖好,可不要濫用哦!
Chrome 提供的 Break On
功能看起來就像是 MutationObserver
的精簡版,很是實用。
關注微信公衆號:創宇前端(KnownsecFED),碼上獲取更多優質乾貨!