JavaScript中MutationObServer監聽DOM元素詳情

一、基本使用

可以通過MutationObserver構造函數實例化,參數是一個回調函數。

let observer = new MutationObserver(() => console.log("change"));

console.log(observer);

observer對象原型鏈如下:

MutationObserver實例:

可以看到有disconnectobservertakeRecords方法。

1. observer方法監聽

observer方法用於關聯DOM元素,並根據相關設置進行監聽。

語法如下:

// 接收兩個參數

observer(DOM元素, MutationObserverInit對象);

其中:

  • 第一個參數DOM元素就是頁面元素,比如:body、div等。
  • 第二個參數就是設置要監聽的范圍。比如:屬性、文本、子節點等,是一個鍵值對數組。

示例1,監聽body元素class的變化:

let observer = new MutationObserver(() => console.log("change"));

// 監聽body元素的屬性變化

observer.observe(document.body, {

    attributes: true

});

// 更改body元素的class,會異步執行創建MutationObserver對象時傳入的回調函數

document.body.className = "main";

console.log("修改瞭body屬性");

// 控制臺輸出:

//    修改瞭body屬性

//    change

上面 change 的輸出是在 修改瞭body屬性 之後,可見註冊的回調函數是異步執行的,是在後面執行的。

2. 回調函數增加MutationRecord實例數組參數

現在回調函數非常簡單,就是輸出一個字符串,看不出到底發生瞭什麼變化。

其實回調函數接收一個 MutationRecord 實例數組,實務中可以通過這個查看詳細的信息。

let observer = new MutationObserver(

    // 回調函數是一個 MutationRecord 實例數組。格式如下:

    //     [MutationRecord, MutationRecord, MutationRecord, ...]

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

document.body.className = "main";

console.log("修改瞭body屬性");

// 控制臺輸出:

//    修改瞭body屬性

//     (1) [MutationRecord]

其中 mutationRecords信息 如下:

MutationRecord實例

其中幾個比較關鍵的信息:

  • attributeName 表示修改的屬性名稱
  • target 修改的目標
  • type 類型

如果多次修改body的屬性,那麼會有多條記錄:

// MutationRecord

let observer = new MutationObserver(

    // 回調函數接收一個 MutationRecord 實例,是一個數組。

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

 

// 修改三次

document.body.className = "main";

document.body.className = "container";

document.body.className = "box";

 

// 控制臺打印如下:

//     (3) [MutationRecord, MutationRecord, MutationRecord]

註意:

這裡不是修改一次就執行一次回調,而是每修改一次就往 mutationRecords 參數加入一個 MutationRecord 實例,最後執行一次回調打印出來。

如果修改一次就執行一次回調,那麼性能就會比較差。

3. disconnect方法終止回調

如果要終止回調,可以使用disconnect方法。

let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

// 第一次修改

document.body.className = "main";

 

// 終止

observer.disconnect();

 

// 第二次修改

document.body.className = "container";

 

// 沒有日志輸出

這裡沒有日志輸出,包括第一次修改也沒有日志輸出,因為回調函數的執行是異步的,是在最後執行的。後面把observer終止瞭,所以就不會執行瞭。

可以用setTimeout控制最後才終止,這樣回調就會正常執行。

let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

// 第一次修改

document.body.className = "main";

 

// 終止

setTimeout(() => {

    observer.disconnect();

    // 第三次修改,下面修改不會回調瞭

    document.body.className = "container";

}, 0);

 

// 第二次修改

document.body.className = "container";

 

// 頁面輸出:

//    (2) [MutationRecord, MutationRecord]

終止之後再啟用

終止瞭之後可以再次啟動,請看下面示例:

let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

// 第一次修改,會入 mutationRecords 數組

document.body.className = "main";

// 終止

setTimeout(() => {

    observer.disconnect();

    // 第二次修改,因為終止瞭,下面修改不會入 mutationRecords 數組

    document.body.className = "container";

}, 0);

setTimeout(() => {

    // 再次啟用

    observer.observe(document.body, {

        attributes: true

    });

    // 修改body屬性,會入 mutationRecords 數組

    document.body.className = "container";

}, 0);

 

// 控制臺輸出:

//    [MutationRecord]

//    [MutationRecord]

這邊回調函數是執行瞭兩次,打印瞭兩個,其中:

  • 第一個輸出是在第一次修改,後面沒有同步代碼瞭,就執行瞭回調。
  • 第二個輸出是在第三次修改,因為重新啟用瞭,所以就正常執行瞭回調。

第二次修改,因為observer被終止瞭,所以修改body的屬性不會入 mutationRecords 數組。

4. takeRecords方法獲取修改記錄

如果希望在終止observer之前,對已有的 mutationRecords 記錄進行處理,可以用takeRecords方法獲取。

let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

 

// 第一次修改,會入 mutationRecords 數組

document.body.className = "main";

// 第二次修改,會入 mutationRecords 數組

document.body.className = "container";

// 第三次修改,會入 mutationRecords 數組

document.body.className = "box";

// 取到修改記錄,可以對其進行處理

let mutationRecords =  observer.takeRecords();

console.log(mutationRecords);

// 控制臺打印:

//     (3) [MutationRecord, MutationRecord, MutationRecord]

console.log(observer.takeRecords());

// 控制臺打印:

//    []

// 終止

observer.disconnect();

二、監聽多個元素

上面監聽都是隻有一個元素,如果要監聽多個元素可以復用MutationObserver實例

let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

// 創建 div1 元素,並監聽

let div1 = document.createElement("div");

observer.observe(div1, {

    attributes: true

});

div1.id = "box1";

 

// 創建div2並監聽

let div2 = document.createElement("div");

observer.observe(div2, {

    attributes: true

});

div2.id = "box2";

 

// 控制臺打印:

//    (2) [MutationRecord, MutationRecord]

控制臺打印瞭兩個MutationRecord,其中:

  • 第一個 MutationRecord 就是 div1 的id屬性修改記錄。
  • 第二個 MutationRecord 就是 div2 的id屬性修改記錄。

其他使用方式和上面的類似。

三、監聽范圍MutationObserverInit對象

上面的監聽都是監聽屬性,當然也可以監聽其他的東西,比如:文本、子節點等。

1. 觀察屬性

上面的例子都是觀察元素自有的屬性,這裡再舉一個自定義屬性的例子。

let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true

});

// 修改自定義的屬性

document.body.setAttribute("data-id", 1);

 

// 控制臺打印:

//    [MutationRecord]

修改自定義的屬性一樣會加入到 mutationRecords 數組。

另外值的一提的是 data-id 經常用來給元素標記一些數據啥的,如果發生變化,程序就可以監聽到,就可以處理一些相應的邏輯。

attributeFilter過濾:

如果要監聽指定的屬性變化,可以用 attributeFilter 過濾。

let observer = new MutationObserver(

    (mutationRecords) => console.log(mutationRecords)

);

observer.observe(document.body, {

    attributes: true,

    // 設置白名單

    attributeFilter: ["data-id"]

});

 

// 修改白名單 attributeFilter 內的屬性,會入 mutationRecords

document.body.setAttribute("data-id", 1);

 

// 修改不在白名單 attributeFilter 內的屬性,不會入 mutationRecords

document.body.setAttribute("class", "main");

 

// 控制臺打印:

//    [MutationRecord]

attributeOldValue記錄舊值

如果要記錄舊值,可以設置 attributeOldValue true

let observer = new MutationObserver(

    // MutationRecord對象中oldValue表示舊值

    (mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue))

);

observer.observe(document.body, {

    attributes: true,

    attributeOldValue: true,

});

// 第一次修改,因為原來沒有值,所以舊值 oldValue = null

document.body.setAttribute("class", "main");

// 第二次修改,因為前面有改瞭一次,所以舊值 oldValue = main

document.body.setAttribute("class", "container");

 

// 控制臺打印:

//    (2) [null, 'main']

2. 觀察文本

觀察文本設置 characterDatatrue 即可,不過隻能觀察文本節點。

請看如下示例:

<!-- 一個性感的div -->

<div id="box">Hello</div>

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

    // 獲取文本節點

    let textNode = document.getElementById("box").childNodes[0];

    observer.observe(textNode, {

        // 觀察文本變化

        characterData: true

    });

    // 修改文本

    textNode.textContent = "Hi";

 

    // 控制臺打印:

    //    [MutationRecord]

</script>

如果直接監聽div元素,那麼是不生效的:

<!-- 一個性感的div -->

<div id="box">Hello</div>

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

    // 監聽div不會生效

    let box = document.getElementById("box");

    observer.observe(box, {

        characterData: true

    });

    box.textContent = "Hi";

 

    // 控制臺無輸出

</script>

characterDataOldValue記錄舊值:

如果要記錄文本舊值,可以設置 characterDataOldValuetrue

<!-- 一個性感的div -->

<div id="box">Hello</div>

 

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue))

    );

    // 獲取文本節點

    let textNode = document.getElementById("box").childNodes[0];

    observer.observe(textNode, {

        // 觀察文本變化

        characterData: true,

        // 保留舊數據

        characterDataOldValue: true,

    });

    // 修改文本兩次

    textNode.textContent = "Hi";

    textNode.textContent = "Nice";

 

    // 控制臺打印:

    //    (2) ['Hello', 'Hi']

</script>

因為div內的內容原本為Hello,先修改為Hi,又修改為Nice,所以兩次修改的舊值就為:Hello 和 Hi 瞭。

3. 觀察子節點

MutationObserver 實例也可以觀察目標節點子節點的變化。

<!-- 一個性感的div -->

<div id="box">Hello</div>

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

    // 獲取div

    let box = document.getElementById("box");

    observer.observe(box, {

        // 觀察子節點變化

        childList: true,

    });

    // 添加元素

    let span = document.createElement("span")

    span.textContent = "world";

    box.appendChild(span);

 

    // 控制臺打印:

    //    [MutationRecord]

</script>

MutationRecord中的addedNodes屬性記錄瞭增加的節點。

移除節點:

<!-- 一個性感的div -->

<div id="box">Hello</div>

 

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

 

    // 獲取div

    let box = document.getElementById("box");

    observer.observe(box, {

        // 觀察子節點變化

        childList: true,

    });

    // 移除第一個子節點,就是Hello文本節點

    box.removeChild(box.childNodes[0]);

 

    // 控制臺打印:

    //    [MutationRecord]

</script>

MutationRecord中的removedNodes屬性記錄瞭移除的節點。

移動節點:

對於已有的節點進行移動,那麼會記錄兩條MutationRecord記錄,因為移動現有的節點是先刪除,後添加。

<!-- 一個性感的div -->

<div id="box">Hello<span>world</span></div>

 

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

 

    // 獲取div

    let box = document.getElementById("box");

    observer.observe(box, {

        // 觀察子節點變化

        childList: true,

    });

    // 將span節點移動到Hello節點前面

    box.insertBefore(box.childNodes[1], box.childNodes[0]);

    // 移動節點,實際是先刪除,後添加。

 

    // 控制臺打印:

    //    (2) [MutationRecord, MutationRecord]

</script>

4. 觀察子樹

上面觀察的節點都是當前設置的目標節點,比如body,就隻能觀察body元素和其子節點的變化。

如果要觀察body及其所有後代節點的變化,那麼可以設置subtree屬性為true

<!-- 一個性感的div -->

<div id="box">Hello<span>world</span></div>

 

<script type="text/javascript">

    let observer = new MutationObserver(

        (mutationRecords) => console.log(mutationRecords)

    );

 

    let box = document.getElementById("box");

    observer.observe(box, {

        attributes: true,

        // 觀察子樹的變化

        subtree: true

    });

    // span元素的id屬性變化就可以觀察到

    box.childNodes[1].id = "text";

    // 控制臺打印:

    //    [MutationRecord]

</script>

subtree設置為true後,不光div元素本身,span元素也可以觀察到瞭。

總結:

  • 1. MutationObserver實例可以用來觀察對象。
  • 2. MutationRecord實例記錄瞭每一次的變化。
  • 3. 回調函數需要所有腳本任務完成後,才會執行,即采用異步方式。
  • 4. 可以觀察的訪問有屬性、文本、子節點、子樹。

到此這篇關於JavaScriptMutationObServer監聽DOM元素詳情的文章就介紹到這瞭,更多相關JavaScript中MutationObServer監聽DOM元素內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: