本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!html
瀏覽器經過事件循環機制使頁面「活」起來,在事件循環中宏任務和微任務有不一樣的執行時機,而瀏覽器基於微任務的技術有 MutationObserver
、Promise
以及以 Promise
爲基礎開發出來的不少其餘的技術。前端
前面的文章也花了很大的篇幅介紹 Promise
,那麼這篇文章就帶你們瞭解一下 MutationObserver
這個微任務是什麼?用來作什麼的吧!vue
MutationObserver
是用來監聽 DOM 變化的一套方法,而監聽 DOM 變化一直是前端工程師一項很是核心的需求。好比不少 Web 應用都利用 HTML 與 JavaScript 構建其自定義控件,與一些內置控件不一樣,這些控件不是固有的。爲了與內置控件一塊兒良好地工做,這些控件必須可以適應內容更改、響應事件和用戶交互。所以,Web 應用須要監視 DOM 變化並及時地作出響應。react
MutationObserver
是如今監聽 DOM 變化的方法,那麼在開始的時候是怎麼監聽的呢,瞭解監聽 DOM 方法的演變有助於咱們更加深刻地理解瀏覽器是怎樣運行的。git
在早期,瀏覽器並無提供對監聽 DOM 的支持,因此那個時候要觀察 DOM 是否變化,惟一能作的即是 輪詢檢測,好比使用 setTimeout
或者 setInterval
來定時檢測 DOM 是否有改變。github
這種方式簡單粗暴,可是會遇到兩個問題:web
在 2000 年的時候引入了 Mutation Event
,它是在 DOM3 中定義的用於監聽 DOM 樹結構變化的事件,不過因爲該事件存在兼容性以及性能上的問題已經被棄用。後端
Mutation Event
總共有7種事件:DOMNodeInserted
、DOMNodeRemoved
、DOMSubtreeModified
、DOMAttrModified
、DOMCharacterDataModified
、DOMNodeInsertedIntoDocument
和DOMNodeRemovedFromDocument
。設計模式
簡單用法以下:數組
let box = document.getElementById('box')
box.addEventListener("DOMSubtreeModified", function () {
console.log('box 元素被修改');
}, false);
複製代碼
Mutation Event
採用了 觀察者的設計模式,當 DOM 有變更時就會馬上觸發相應的事件,這種方式屬於 同步回調。
採用 Mutation Event
解決了 實時性 的問題,由於 DOM 一旦發生變化,就會當即調用 JavaScript 接口。可是 這種實時性形成了嚴重的性能問題,由於每次 DOM 變更,渲染引擎都會去調用 JS,這樣會產生較大的性能開銷。
好比利用 JS 動態建立或動態修改 50
個節點內容,就會觸發 50
次回調,並且每一個回調函數都須要必定的執行時間,這裏咱們假設每次回調的執行時間是 4ms
,那麼 50
次回調的執行時間就是 200ms
,若此時瀏覽器正在執行一個動畫效果,因爲 Mutation Event
觸發回調事件,就會致使動畫的卡頓。
也正是由於使用 Mutation Event
會致使頁面性能問題,因此 Mutation Event
被反對使用,並逐步從 Web 標準事件中刪除了。
MutationObserver
API 能夠用來監視 DOM 的變化,包括屬性的變化、節點的增減、內容的變化等。
MutationObserver
的使用參考 MutationObserver
的 MDN 官方文檔資料
MutationObserver
是一個構造器,用來實例化一個 Mutation
觀察者對象,參數是一個回調函數,這個回調函數會在指定的 DOM 節點發送變化後執行,回調函數有兩個參數:
mutations
:節點變化記錄數組(MutationRecord
)observer
:觀察者對象自己let observe = new MutationObserver(function (mutations, observer) {});
複製代碼
MutationObserver
實例對象有三個方法,以下:
observe
:配置 MutationObserver
在 DOM 更改匹配給定選項時,經過其回調函數開始接收通知。即設置觀察目標,接受兩個參數:
target
:觀察目標;options
:經過對象成員來設置觀察選項disconnect
:阻止 MutationObserver
實例繼續接收的通知,直到再次調用其 observe()
方法,該觀察者對象包含的回調函數都不會再被調用。takeRecords
:從 MutationObserver
的通知隊列中刪除全部待處理的通知,並將它們返回到MutationRecord
對象的新 Array
中。即清空記錄隊列並返回裏面的內容。使用實例:
// 選擇須要觀察變更的節點
const targetNode = document.getElementById('box');
// 觀察器的配置(須要觀察什麼變更)
const config = {
attributes: true,
childList: true,
subtree: true
};
// 當觀察到變更時執行的回調函數
const callback = function (mutationsList, observer) {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('有節點發生改變,當前節點的內容是:' + mutation.target.innerHTML);
} else if (mutation.type === 'attributes') {
console.log('修改了' + mutation.attributeName + '屬性');
}
}
};
// 建立一個觀察器實例並傳入回調函數
const observer = new MutationObserver(callback);
// 以上述配置開始觀察目標節點
observer.observe(targetNode, config);
// 以後,可中止觀察
// observer.disconnect();
複製代碼
MutationObserver
的改進優化MutationObserver
將響應函數改爲異步調用,能夠不用在每次 DOM 變化都觸發異步調用,而是等屢次 DOM 變化後,一次觸發異步調用,而且還會使用一個數據結構來記錄這期間全部的 DOM 變化。這樣即便頻繁地操縱 DOM,也不會對性能形成太大的影響。綜上所述,MutationObserver
採用了 異步 + 微任務 的策略來實現監聽 DOM 的變化。
MutationObserver
和 Vue 中的 nextTick
Vue 中 nextTick
可讓咱們在下次 DOM 更新循環結束以後執行延遲迴調,用於得到更新後的 DOM。
那在 Vue 中是怎麼實現 nextTick
的呢?
Vue 在更新 DOM 時是異步執行的。只要偵聽到數據變化,Vue 將開啓一個隊列,並緩衝在同一事件循環中發生的全部數據變動。若是同一個 watcher
被屢次觸發,只會被推入到隊列中一次。這種在緩衝時去除重複數據對於避免沒必要要的計算和 DOM 操做是很是重要的。而後,在下一個的事件循環「tick」中,Vue 刷新隊列並執行實際 (已去重的) 工做。
而異步回調咱們知道有宏任務(macrotasks
)和微任務(microtasks
)兩種,那爲了讓 nextTick
更快的執行,那確定是優先選擇微任務(microtasks
)的。要建立一個新的微任務(microtask
),會優先使用 Promise
,若是瀏覽器不支持,再嘗試 MutationObserver
。實在不支持,就只能用 setTimeout
這個宏任務了。
Vue 中的異步更新隊列 是這樣說的:
至於 MutationObserver
是怎麼模擬 nextTick
的,能夠看 源碼,其實就是建立一個 TextNode
並監聽內容變化,而後要 nextTick
的時候去改一下這個節點的文本內容:
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
複製代碼
這篇文章介紹了監聽 DOM 變化技術方案的演化史,從輪詢到 Mutation Event
再到最新使用的 MutationObserver
。MutationObserver
方案的核心就是採用微任務機制,有效地權衡了實時性和執行效率的問題。
最後還簡單介紹了 MutationObserver
和 Vue 中 nextTick
的關係。