是誰動(dòng)了我的 DOM?
在某些場(chǎng)景下,我們希望能監(jiān)視 DOM 樹的變動(dòng),然后做一些相關(guān)的操作。比如監(jiān)聽元素被插入 DOM 或從 DOM 樹中移除,然后添加相應(yīng)的動(dòng)畫效果。或者在富文本編輯器中輸入特殊的符號(hào),如 # 或 @ 符號(hào)時(shí)自動(dòng)高亮后面的內(nèi)容等。
要實(shí)現(xiàn)這些功能,我們就可以考慮使用 MutationObserver API,接下來阿寶哥將帶大家一起來探索 MutationObserver API 所提供的強(qiáng)大能力。
閱讀完本文,你將了解以下內(nèi)容:
MutationObserver 是什么; MutationObserver API 的基本使用及 MutationRecord 對(duì)象; MutationObserver API 常見的使用場(chǎng)景; 什么是觀察者設(shè)計(jì)模式及如何使用 TS 實(shí)現(xiàn)觀察者設(shè)計(jì)模式。
一、MutationObserver 是什么
MutationObserver 接口提供了監(jiān)視對(duì) DOM 樹所做更改的能力。它被設(shè)計(jì)為舊的 Mutation Events 功能的替代品,該功能是 DOM3 Events 規(guī)范的一部分。
利用 MutationObserver API 我們可以監(jiān)視 DOM 的變化。DOM 的任何變化,比如節(jié)點(diǎn)的增加、減少、屬性的變動(dòng)、文本內(nèi)容的變動(dòng),通過這個(gè) API 我們都可以得到通知。
MutationObserver 有以下特點(diǎn):
它等待所有腳本任務(wù)執(zhí)行完成后,才會(huì)運(yùn)行,它是異步觸發(fā)的。即會(huì)等待當(dāng)前所有 DOM 操作都結(jié)束才觸發(fā),這樣設(shè)計(jì)是為了應(yīng)對(duì) DOM 頻繁變動(dòng)的問題。 它把 DOM 變動(dòng)記錄封裝成一個(gè)數(shù)組進(jìn)行統(tǒng)一處理,而不是一條一條進(jìn)行處理。 它既可以觀察 DOM 的所有類型變動(dòng),也可以指定只觀察某一類變動(dòng)。
二、MutationObserver API 簡(jiǎn)介
在介紹 MutationObserver API 之前,我們先來了解一下它的兼容性:

(圖片來源:https://caniuse.com/#search=MutationObserver)
從上圖可知,目前主流的 Web 瀏覽器基本都支持 MutationObserver API,而對(duì)于 IE 瀏覽器只有 IE 11 才支持。在項(xiàng)目中,如需要使用 ?MutationObserver API,首先我們需要?jiǎng)?chuàng)建 MutationObserver 對(duì)象,因此接下來我們來介紹 MutationObserver 構(gòu)造函數(shù)。
DOM 規(guī)范中的 MutationObserver 構(gòu)造函數(shù),用于創(chuàng)建并返回一個(gè)新的觀察器,它會(huì)在觸發(fā)指定 DOM 事件時(shí),調(diào)用指定的回調(diào)函數(shù)。MutationObserver 對(duì) DOM 的觀察不會(huì)立即啟動(dòng),而必須先調(diào)用 observe() 方法來指定所要觀察的 DOM 節(jié)點(diǎn)以及要響應(yīng)哪些更改。
2.1 構(gòu)造函數(shù)
MutationObserver 構(gòu)造函數(shù)的語法為:
const?observer?=?new?MutationObserver(callback);
相關(guān)的參數(shù)說明如下:
callback:一個(gè)回調(diào)函數(shù),每當(dāng)被指定的節(jié)點(diǎn)或子樹有發(fā)生 DOM 變動(dòng)時(shí)會(huì)被調(diào)用。該回調(diào)函數(shù)包含兩個(gè)參數(shù):一個(gè)是描述所有被觸發(fā)改動(dòng)的 MutationRecord 對(duì)象數(shù)組,另一個(gè)是調(diào)用該函數(shù)的 MutationObserver 對(duì)象。
使用示例
const?observer?=?new?MutationObserver(function?(mutations,?observer)?{
??mutations.forEach(function(mutation)?{
????console.log(mutation);
??});
});
2.2 方法
disconnect():阻止 MutationObserver 實(shí)例繼續(xù)接收通知,除非再次調(diào)用其 observe() 方法,否則該觀察者對(duì)象包含的回調(diào)函數(shù)都不會(huì)再被調(diào)用。
observe(target[, options]):該方法用來啟動(dòng)監(jiān)聽,它接受兩個(gè)參數(shù)。第一個(gè)參數(shù),用于指定所要觀察的 DOM 節(jié)點(diǎn)。第二個(gè)參數(shù),是一個(gè)配置對(duì)象,用于指定所要觀察的特定變動(dòng)。
const?editor?=?document.querySelector('#editor');
const?options?=?{
??childList:?true,?//?監(jiān)視node直接子節(jié)點(diǎn)的變動(dòng)
??subtree:?true,?//?監(jiān)視node所有后代的變動(dòng)
??attributes:?true,?//?監(jiān)視node屬性的變動(dòng)
??characterData:?true,?//?監(jiān)視指定目標(biāo)節(jié)點(diǎn)或子節(jié)點(diǎn)樹中節(jié)點(diǎn)所包含的字符數(shù)據(jù)的變化。
??attributeOldValue:?true?//?記錄任何有改動(dòng)的屬性的舊值
};
observer.observe(article,?options);takeRecords():返回已檢測(cè)到但尚未由觀察者的回調(diào)函數(shù)處理的所有匹配 DOM 更改的列表,使變更隊(duì)列保持為空。此方法最常見的使用場(chǎng)景是:在斷開觀察者之前立即獲取所有未處理的更改記錄,以便在停止觀察者時(shí)可以處理任何未處理的更改。
2.3 MutationRecord 對(duì)象
DOM 每次發(fā)生變化,就會(huì)生成一條變動(dòng)記錄,即 MutationRecord 實(shí)例。該實(shí)例包含了與變動(dòng)相關(guān)的所有信息。Mutation Observer 對(duì)象處理的就是一個(gè)個(gè) MutationRecord 實(shí)例所組成的數(shù)組。
MutationRecord 實(shí)例包含了變動(dòng)相關(guān)的信息,含有以下屬性:
type:變動(dòng)的類型,值可以是 attributes、characterData 或 childList; target:發(fā)生變動(dòng)的 DOM 節(jié)點(diǎn); addedNodes:返回新增的 DOM 節(jié)點(diǎn),如果沒有節(jié)點(diǎn)被添加,則返回一個(gè)空的 NodeList; removedNodes:返回移除的 DOM 節(jié)點(diǎn),如果沒有節(jié)點(diǎn)被移除,則返回一個(gè)空的 NodeList; previousSibling:返回被添加或移除的節(jié)點(diǎn)之前的兄弟節(jié)點(diǎn),如果沒有則返回 null;nextSibling:返回被添加或移除的節(jié)點(diǎn)之后的兄弟節(jié)點(diǎn),如果沒有則返回 null;attributeName:返回被修改的屬性的屬性名,如果設(shè)置了 attributeFilter,則只返回預(yù)先指定的屬性;attributeNamespace:返回被修改屬性的命名空間; oldValue:變動(dòng)前的值。這個(gè)屬性只對(duì) attribute和characterData變動(dòng)有效,如果發(fā)生childList變動(dòng),則返回null。
2.4 MutationObserver 使用示例
<html?lang="zh-CN">
??<head>
????<meta?charset="UTF-8"?/>
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
????<title>DOM?變動(dòng)觀察器示例title>
????<style>
??????.editor?{border:?1px?dashed?grey;?width:?400px;?height:?300px;}
????style>
??head>
??<body>
????<h3>阿寶哥:DOM 變動(dòng)觀察器(Mutation observer)h3>
????<div?contenteditable?id="container"?class="editor">大家好,我是阿寶哥!div>
????<script>
??????const?containerEle?=?document.querySelector("#container");
??????let?observer?=?new?MutationObserver((mutationRecords)?=>?{
????????console.log(mutationRecords);?//?輸出變動(dòng)記錄
??????});
??????observer.observe(containerEle,?{
????????subtree:?true,?//?監(jiān)視node所有后代的變動(dòng)
????????characterDataOldValue:?true,?//?記錄任何有變動(dòng)的屬性的舊值
??????});
????script>
??body>
html>
以上代碼成功運(yùn)行之后,阿寶哥對(duì) id 為 container 的 div 容器中原始內(nèi)容進(jìn)行修改,即把 大家好,我是阿寶哥! 修改為 大家好,我。對(duì)于上述的修改,控制臺(tái)將會(huì)輸出 5 條變動(dòng)記錄,這里我們來看一下最后一條變動(dòng)記錄:

MutationObserver 對(duì)象的 observe(target [, options]) 方法支持很多配置項(xiàng),這里阿寶哥就不詳細(xì)展開介紹了。
但是為了讓剛接觸 MutationObserver API 的小伙伴能更直觀的感受每個(gè)配置項(xiàng)的作用,阿寶哥把 mutationobserver-api-guide 這篇文章中使用的在線示例統(tǒng)一提取出來,做了一下匯總與分類:
1、MutationObserver Example - childList:https://codepen.io/impressivewebs/pen/aXVVjg
2、MutationObserver Example - childList with subtree:https://codepen.io/impressivewebs/pen/PVgyLa
3、MutationObserver Example - Attributes:https://codepen.io/impressivewebs/pen/XOzaWv
4、MutationObserver Example - Attribute Filter:https://codepen.io/impressivewebs/pen/pGGdVr
5、MutationObserver Example - attributeFilter with subtree:https://codepen.io/impressivewebs/pen/ywYaYv
6、MutationObserver Example - characterData:https://codepen.io/impressivewebs/pen/pGdpvq
7、MutationObserver Example - characterData with subtree:https://codepen.io/impressivewebs/pen/bZVpMZ
8、MutationObserver Example - Recording an Old Attribute Value:https://codepen.io/impressivewebs/pen/wNNjrP
9、MutationObserver Example - Recording old characterData:https://codepen.io/impressivewebs/pen/aXrzex
10、MutationObserver Example - Multiple Changes for a Single Observer:https://codepen.io/impressivewebs/pen/OqJMeG
11、MutationObserver Example - Moving a Node Tree:https://codepen.io/impressivewebs/pen/GeRWPX
三、MutationObserver 使用場(chǎng)景
3.1 語法高亮
相信大家對(duì)語法高亮都不會(huì)陌生,平時(shí)在閱讀各類技術(shù)文章時(shí),都會(huì)遇到它。接下來,阿寶哥將跟大家介紹如何使用 MutationObserver API 和 Prism.js 這個(gè)庫實(shí)現(xiàn) JavaScript 和 CSS 語法高亮。
在看具體的實(shí)現(xiàn)代碼前,我們先來看一下以下 HTML 代碼段未語法高亮和語法高亮的區(qū)別:
let?htmlSnippet?=?`下面是一個(gè)JavaScript代碼段:
????
????????let?greeting?=?"大家好,我是阿寶哥";?
????
????另一個(gè)CSS代碼段:
???????
?????????
????????????#code-container?{?border:?1px?dashed?grey;?padding:?5px;?}?
?????????
????
`

通過觀察上圖,我們可以很直觀地發(fā)現(xiàn),有進(jìn)行語法高亮的代碼塊閱讀起來更加清晰易懂。下面我們來看一下實(shí)現(xiàn)語法高亮的功能代碼:
<html?lang="zh-CN">
??<head>
????<meta?charset="UTF-8"?/>
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
????<title>MutationObserver?實(shí)戰(zhàn)之語法高亮title>
????<style>
??????#code-container?{
????????border:?1px?dashed?grey;
????????padding:?5px;
????????width:?550px;
????????height:?200px;
??????}
????style>
????<link?href="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/themes/prism.min.css"?rel="stylesheet">
????<script?src="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/prism.min.js"?data-manual>script>
????<script?src="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/components/prism-javascript.min.js">script>
????<script?src="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/components/prism-css.min.js">script>
??head>
??<body>
????<h3>阿寶哥:MutationObserver 實(shí)戰(zhàn)之語法高亮h3>
????<div?id="code-container">div>
????<script>
??????let?observer?=?new?MutationObserver((mutations)?=>?{
????????for?(let?mutation?of?mutations)?{
??????????//?獲取新增的DOM節(jié)點(diǎn)
??????????for?(let?node?of?mutation.addedNodes)?{
????????????//?只處理HTML元素,跳過其他節(jié)點(diǎn),比如文本節(jié)點(diǎn)
????????????if?(!(node?instanceof?HTMLElement))?continue;
????????????//?檢查插入的節(jié)點(diǎn)是否為代碼段
????????????if?(node.matches('pre[class*="language-"]'))?{
??????????????Prism.highlightElement(node);
????????????}
????????????//?檢查插入節(jié)點(diǎn)的子節(jié)點(diǎn)是否為代碼段
????????????for?(let?elem?of?node.querySelectorAll('pre[class*="language-"]'))?{
??????????????Prism.highlightElement(elem);
????????????}
??????????}
????????}
??????});
??????let?codeContainer?=?document.querySelector("#code-container");
??????observer.observe(codeContainer,?{?childList:?true,?subtree:?true?});
??????//?動(dòng)態(tài)插入帶有代碼段的內(nèi)容
??????codeContainer.innerHTML?=?`下面是一個(gè)JavaScript代碼段:
?????????let?greeting?=?"大家好,我是阿寶哥";?
????????另一個(gè)CSS代碼段:
????????
??????????
?????????????#code-container?{?border:?1px?dashed?grey;?padding:?5px;?}?
??????????
????????
????????`;
????script>
??body>
html>
在以上代碼中,首先我們?cè)谝?prism.min.js 的 script 標(biāo)簽上設(shè)置 data-manual 屬性,用于告訴 Prism.js 我們將使用手動(dòng)模式來處理語法高亮。
接著我們?cè)诨卣{(diào)函數(shù)中通過獲取 mutation 對(duì)象的 addedNodes 屬性來進(jìn)一步獲取新增的 DOM 節(jié)點(diǎn)。然后我們遍歷新增的 DOM 節(jié)點(diǎn),判斷新增的 DOM 節(jié)點(diǎn)是否為代碼段,如果滿足條件的話則進(jìn)行高亮操作。
此外,除了判斷當(dāng)前節(jié)點(diǎn)之外,我們也會(huì)判斷插入節(jié)點(diǎn)的子節(jié)點(diǎn)是否為代碼段,如果滿足條件的話,也會(huì)進(jìn)行高亮操作。
3.2 監(jiān)聽元素的 load 或 unload 事件
對(duì) Web 開發(fā)者來說,相信很多人對(duì) load 事件都不會(huì)陌生。當(dāng)整個(gè)頁面及所有依賴資源如樣式表和圖片都已完成加載時(shí),將會(huì)觸發(fā) load 事件。而當(dāng)文檔或一個(gè)子資源正在被卸載時(shí),會(huì)觸發(fā) unload 事件。
在日常開發(fā)過程中,除了監(jiān)聽頁面的加載和卸載事件之外,我們經(jīng)常還需要監(jiān)聽 DOM 節(jié)點(diǎn)的插入和移除事件。比如當(dāng) DOM 節(jié)點(diǎn)插入 DOM 樹中產(chǎn)生插入動(dòng)畫,而當(dāng)節(jié)點(diǎn)從 DOM 樹中被移除時(shí)產(chǎn)生移除動(dòng)畫。針對(duì)這種場(chǎng)景我們就可以利用 MutationObserver API 來監(jiān)聽元素的添加與移除。
同樣,在看具體的實(shí)現(xiàn)代碼前,我們先來看一下實(shí)際的效果:

在以上示例中,當(dāng)點(diǎn)擊 跟蹤元素生命周期 按鈕時(shí),一個(gè)新的 DIV 元素會(huì)被插入到 body 中,成功插入后,會(huì)在消息框顯示相關(guān)的信息。在 3S 之后,新增的 DIV 元素會(huì)從 DOM 中移除,成功移除后,會(huì)在消息框中顯示 元素已從DOM中移除了 的信息。
下面我們來看一下具體實(shí)現(xiàn):
index.html
<html?lang="zh-CN">
??<head>
????<meta?charset="UTF-8"?/>
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>
????<title>MutationObserver?load/unload?事件title>
????<link
??????rel="stylesheet"
??????href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.0.0/animate.min.css"
????/>
??head>
??<body>
????<h3>阿寶哥:MutationObserver load/unload 事件h3>
????<div?class="block">
??????<p>
????????<button?onclick="trackElementLifecycle()">跟蹤元素生命周期button>
??????p>
??????<textarea?id="messageContainer"?rows="5"?cols="50">textarea>
????div>
????<script?src="./on-load.js">script>
????<script>
??????const?busy?=?false;
??????const?messageContainer?=?document.querySelector("#messageContainer");
??????function?trackElementLifecycle()?{
????????if?(busy)?return;
????????const?div?=?document.createElement("div");
????????div.innerText?=?"我是新增的DIV元素";
????????div.classList.add("animate__animated",?"animate__bounceInDown");
????????watchElement(div);
????????document.body.appendChild(div);
??????}
??????function?watchElement(element)?{
????????onload(
??????????element,
??????????function?(el)?{
????????????messageContainer.value?=?"元素已被添加到DOM中,?3s后將被移除";
????????????setTimeout(()?=>?document.body.removeChild(el),?3000);
??????????},
??????????function?(el)?{
????????????messageContainer.value?=?"元素已從DOM中移除了";
??????????}
????????);
??????}
????script>
??body>
html>
on-load.js
//?只包含部分代碼
const?watch?=?Object.create(null);
const?KEY_ID?=?"onloadid"?+?Math.random().toString(36).slice(2);
const?KEY_ATTR?=?"data-"?+?KEY_ID;
let?INDEX?=?0;
if?(window?&&?window.MutationObserver)?{
??const?observer?=?new?MutationObserver(function?(mutations)?{
????if?(Object.keys(watch).length?1)?return;
????for?(let?i?=?0;?i???????if?(mutations[i].attributeName?===?KEY_ATTR)?{
????????eachAttr(mutations[i],?turnon,?turnoff);
????????continue;
??????}
??????eachMutation(mutations[i].removedNodes,?function?(index,?el)?{
????????if?(!document.documentElement.contains(el))?turnoff(index,?el);
??????});
??????eachMutation(mutations[i].addedNodes,?function?(index,?el)?{
????????if?(document.documentElement.contains(el))?turnon(index,?el);
??????});
????}
??});
??observer.observe(document.documentElement,?{
????childList:?true,
????subtree:?true,
????attributes:?true,
????attributeOldValue:?true,
????attributeFilter:?[KEY_ATTR],
??});
}
function?onload(el,?on,?off,?caller)?{
??on?=?on?||?function?()?{};
??off?=?off?||?function?()?{};
??el.setAttribute(KEY_ATTR,?"o"?+?INDEX);
??watch["o"?+?INDEX]?=?[on,?off,?0,?caller?||?onload.caller];
??INDEX?+=?1;
??return?el;
}
on-load.js 的完整代碼:https://gist.github.com/semlinker/a149763bf033d7f2dff2d32d60c27865
3.3 富文本編輯器
除了前面兩個(gè)應(yīng)用場(chǎng)景,在富文本編輯器的場(chǎng)景,MutationObserver API 也有它的用武之地。比如我們希望在富文本編輯器中高亮 # 符號(hào)后的內(nèi)容,這時(shí)候我們就可以通過 MutationObserver API 來監(jiān)聽用戶輸入的內(nèi)容,發(fā)現(xiàn)用戶輸入 # 時(shí)自動(dòng)對(duì)輸入的內(nèi)容進(jìn)行高亮處理。
這里阿寶哥基于 vue-hashtag-textarea 這個(gè)項(xiàng)目來演示一下上述的效果:

此外,MutationObserver API 在 Github 上的一個(gè)名為 Editor.js 的項(xiàng)目中也有應(yīng)用。Editor.js 是一個(gè) Block-Styled 編輯器,以 JSON 格式輸出數(shù)據(jù)的富文本和媒體編輯器。它是完全模塊化的,由 “塊” 組成,這意味著每個(gè)結(jié)構(gòu)單元都是它自己的塊(例如段落、標(biāo)題、圖像都是塊),用戶可以輕松地編寫自己的插件來進(jìn)一步擴(kuò)展編輯器。
在 Editor.js 編輯器內(nèi)部,它通過 MutationObserver API 來監(jiān)聽富文本框的內(nèi)容異動(dòng),然后觸發(fā) change 事件,使得外部可以對(duì)變動(dòng)進(jìn)行響應(yīng)和處理。上述的功能被封裝到內(nèi)部的 modificationsObserver.ts 模塊,感興趣的小伙伴可以閱讀 modificationsObserver.ts 模塊的代碼。
當(dāng)然利用 MutationObserver API 提供的強(qiáng)大能力,我們還可以有其他的應(yīng)用場(chǎng)景,比如防止頁面的水印元素被刪除,從而避免無法跟蹤到 “泄密者” ,當(dāng)然這并不是絕對(duì)的安全,只是多加了一層防護(hù)措施。
具體如何實(shí)現(xiàn)水印元素被刪除,篇幅有限。這里阿寶哥不繼續(xù)展開介紹了,大家可以參考掘金上 “打開控制臺(tái)也刪不掉的元素,前端都嚇尿了” 這一篇文章。
至此 MutationObserver 變動(dòng)觀察者相關(guān)內(nèi)容已經(jīng)介紹完了,既然講到觀察者,阿寶哥情不自禁想再介紹一下觀察者設(shè)計(jì)模式。
四、觀察者設(shè)計(jì)模式
4.1 簡(jiǎn)介
觀察者模式,它定義了一種一對(duì)多的關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象,這個(gè)主題對(duì)象的狀態(tài)發(fā)生變化時(shí)就會(huì)通知所有的觀察者對(duì)象,使得它們能夠自動(dòng)更新自己。
我們可以使用日常生活中,期刊訂閱的例子來形象地解釋一下上面的概念。期刊訂閱包含兩個(gè)主要的角色:期刊出版方和訂閱者,他們之間的關(guān)系如下:
期刊出版方 —— 負(fù)責(zé)期刊的出版和發(fā)行工作。 訂閱者 —— 只需執(zhí)行訂閱操作,新版的期刊發(fā)布后,就會(huì)主動(dòng)收到通知,如果取消訂閱,以后就不會(huì)再收到通知。
在觀察者模式中也有兩個(gè)主要角色:Subject(主題)和 Observer(觀察者),它們分別對(duì)應(yīng)例子中的期刊出版方和訂閱者。接下來我們來看張圖,進(jìn)一步加深對(duì)以上概念的理解。

4.2 模式結(jié)構(gòu)
觀察者模式包含以下角色:
Subject:主題類 Observer:觀察者

4.3 觀察者模式實(shí)戰(zhàn)
4.3.1 定義 Observer 接口
interface?Observer?{
??notify:?Function;
}
4.3.2 創(chuàng)建 ConcreteObserver 觀察者實(shí)現(xiàn)類
class?ConcreteObserver?implements?Observer{
????constructor(private?name:?string)?{}
????notify()?{
??????console.log(`${this.name}?has?been?notified.`);
????}
}
4.3.3 創(chuàng)建 Subject 類
class?Subject?{?
????private?observers:?Observer[]?=?[];
????public?addObserver(observer:?Observer):?void?{
??????console.log(observer,?"is?pushed!");
??????this.observers.push(observer);
????}
????public?deleteObserver(observer:?Observer):?void?{
??????console.log("remove",?observer);
??????const?n:?number?=?this.observers.indexOf(observer);
??????n?!=?-1?&&?this.observers.splice(n,?1);
????}
????public?notifyObservers():?void?{
??????console.log("notify?all?the?observers",?this.observers);
??????this.observers.forEach(observer?=>?observer.notify());
????}
}
4.3.4 使用示例
const?subject:?Subject?=?new?Subject();
const?semlinker?=?new?ConcreteObserver("semlinker");
const?kaquqo?=?new?ConcreteObserver("kakuqo");
subject.addObserver(semlinker);
subject.addObserver(kaquqo);
subject.notifyObservers();
subject.deleteObserver(kaquqo);
subject.notifyObservers();
以上代碼成功運(yùn)行后,控制臺(tái)會(huì)輸出以下結(jié)果:
[LOG]:?{?"name":?"semlinker"?},??is?pushed!?
[LOG]:?{?"name":?"kakuqo"?},??is?pushed!?
[LOG]:?notify?all?the?observers,??[?{?"name":?"semlinker"?},?{?"name":?"kakuqo"?}?]?
[LOG]:?semlinker?has?been?notified.?
[LOG]:?kakuqo?has?been?notified.?
[LOG]:?remove,??{?"name":?"kakuqo"?}?
[LOG]:?notify?all?the?observers,??[?{?"name":?"semlinker"?}?]?
[LOG]:?semlinker?has?been?notified.?
通過觀察以上的輸出結(jié)果,當(dāng)觀察者被移除以后,后續(xù)的通知就接收不到了。觀察者模式支持簡(jiǎn)單的廣播通信,能夠自動(dòng)通知所有已經(jīng)訂閱過的對(duì)象。但如果一個(gè)被觀察者對(duì)象有很多的觀察者的話,將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間。 所以在實(shí)際項(xiàng)目中使用的話,大家需要注意以上的問題。
五、參考資源
MDN - MutationObserver MDN - MutationRecord JavaScript 標(biāo)準(zhǔn)參考教程 - MutationObserver mutationobserver-api-guide javascript.info-mutation-observer
