[英]Performance of MutationObserver to detect nodes in entire DOM
我對使用MutationObserver
檢測某個 HTML 元素是否添加到 HTML 頁面中的任何位置很感興趣。 例如,我會說我想檢測是否在 DOM 中的任何位置添加了任何<li>
。
到目前為止,我看到的所有MutationObserver
示例都只檢測節點是否添加到特定容器中。 例如:
一些 HTML
<body>
...
<ul id='my-list'></ul>
...
</body>
MutationObserver
定義
var container = document.querySelector('ul#my-list');
var observer = new MutationObserver(function(mutations){
// Do something here
});
observer.observe(container, {
childList: true,
attributes: true,
characterData: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true
});
所以在這個例子中, MutationObserver
被設置為觀察一個非常確定的容器 ( ul#my-list
) 以查看是否有任何<li>
附加到它。
如果我想不那么具體,並像這樣在整個 HTML 正文中觀察<li>
是否有問題:
var container = document.querySelector('body');
我知道它適用於我為自己設置的基本示例......但是不建議這樣做嗎? 這會導致性能不佳嗎? 如果是這樣,我將如何檢測和衡量該性能問題?
我想可能有一個原因,所有MutationObserver
示例都針對其目標容器如此具體……但我不確定。
如果在頁面加載/渲染之前附加,未優化的 MutationObserver 回調可以在頁面大而復雜的情況下(例如,5 秒到 7 秒)增加幾秒鍾的頁面加載時間( 1 , 2 )。 回調作為阻止 DOM 進一步處理的微任務執行,並且可以在復雜頁面上每秒觸發數百或數千次。 大多數示例和現有庫都沒有考慮到這種情況,並提供了好看、易於使用但可能會很慢的 JS 代碼。
始終使用devtools 分析器,並嘗試使您的觀察者回調消耗少於頁面加載期間消耗的總 CPU 時間的 1%。
避免通過訪問 offsetTop 和類似屬性觸發強制同步布局
避免使用像 jQuery 這樣復雜的 DOM 框架/庫,更喜歡原生 DOM 的東西
觀察屬性時,請使用.observe()
attributeFilter: ['attr1', 'attr2']
選項。
盡可能以非遞歸方式觀察直接父母( subtree: false
)。
例如,通過遞歸觀察document
來等待父元素是有意義的,成功時斷開觀察者,在這個容器元素上附加一個新的非遞歸的。
當只等待一個具有id
屬性的元素時,使用非常快的getElementById
而不是枚舉mutations
數組(它可能有數千個條目): example 。
例如,如果頁面上所需的元素相對較少(例如iframe
或object
),請使用getElementsByTagName
和getElementsByClassName
返回的實時 HTMLCollection 並重新檢查它們,而不是枚舉超過 100 個元素的mutations
。
避免使用querySelector
,尤其是極慢的querySelectorAll
。
如果在 MutationObserver 回調中絕對無法避免querySelectorAll
,則首先執行querySelector
檢查,如果成功,則繼續querySelectorAll
。 平均而言,這樣的組合會快得多。
如果針對 2018 年之前的 Chrome/ium,請不要使用需要回調的內置數組方法,例如 forEach、filter 等,因為在 Chrome 的 V8 中,與經典的for (var i=0 ....)
循環(慢 10-100 倍),並且 MutationObserver 回調可能會在復雜的現代頁面上報告數千個節點。
如果針對 2019 之前的瀏覽器,請不要在 MutationObserver 回調中使用像for (let v of something)
類的慢 ES2015 循環for (let v of something)
除非您進行編譯以使結果代碼與經典for
循環一樣快地運行。
如果目標是改變頁面的外觀並且您有一種可靠且快速的方法來告訴添加的元素在頁面的可見部分之外,請斷開觀察者的連接並通過setTimeout(fn, 0)
安排整個頁面的重新檢查和重新處理:當解析/布局活動的初始爆發完成並且引擎可以“呼吸”甚至可能需要一秒鍾時,它將被執行。 然后,例如,您可以使用 requestAnimationFrame 以塊的形式不顯眼地處理頁面。
如果處理很復雜和/或需要很多時間,則可能會導致繪制幀很長、無響應/卡頓,因此在這種情況下,您可以使用去抖動或類似技術,例如在外部數組中累積突變並通過以下方式安排運行setTimeout / requestIdleCallback / requestAnimationFrame:
const queue = []; const mo = new MutationObserver(mutations => { if (!queue.length) requestAnimationFrame(process); queue.push(mutations); }); function process() { for (const mutations of queue) { // .......... } queue.length = 0; }
觀察一個非常確定的容器
ul#my-list
以查看是否有任何<li>
附加到它。
由於li
是直接子節點,並且我們尋找添加的節點,因此唯一需要的選項是childList: true
(請參閱上面的建議 #2)。
new MutationObserver(function(mutations, observer) {
// Do something here
// Stop observing if needed:
observer.disconnect();
}).observe(document.querySelector('ul#my-list'), {childList: true});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.