11
22# DOM 变动观察器(Mutation observer)
33
4- ` MutationObserver ` 是一个内置对象,它监控 DOM 元素,在其发生变动时触发回调 。
4+ ` MutationObserver ` 是一个内建对象,它观察 DOM 元素,在其发生更改时触发回调 。
55
6- 我们将首先看一下语法,然后研究实际的用例 。
6+ 我们将首先看一下语法,然后探究一个实际的用例,以了解它在什么地方有用 。
77
88## 语法
99
1010` MutationObserver ` 使用简单。
1111
12- 首先,我们创建一个带有回调函数(callback-function)的观察器 :
12+ 首先,我们创建一个带有回调函数的观察器 :
1313
1414``` js
1515let observer = new MutationObserver (callback);
@@ -21,32 +21,32 @@ let observer = new MutationObserver(callback);
2121observer .observe (node, config);
2222```
2323
24- ` config ` 是一个带布尔选项的对象 ,该布尔选项表示“将对哪些变动做出反应 ”:
25- - ` childList ` - ` node ` 的直接子节点的变动 ,
26- - ` subtree ` - ` node ` 的所有后代节点的变动 ,
27- - ` attributes ` - ` node ` 的属性 ,
28- - ` attributeFilter ` - 一组属性名称,只监控选定的属性 。
29- - ` characterData ` - 是否监测 ` node.data ` (文本内容),
24+ ` config ` 是一个具有布尔选项的对象 ,该布尔选项表示“将对哪些更改做出反应 ”:
25+ - ` childList ` —— ` node ` 的直接子节点的更改 ,
26+ - ` subtree ` —— ` node ` 的所有后代的更改 ,
27+ - ` attributes ` —— ` node ` 的特性(attribute) ,
28+ - ` attributeFilter ` —— 特性名称数组,只观察选定的特性 。
29+ - ` characterData ` —— 是否观察 ` node.data ` (文本内容),
3030
3131其他几个选项:
32- - ` attributeOldValue ` - 如果为 ` true ` ,则将属性的旧值和新值都传递给回调函数 (参见下文),否则只传新值(需要 ` attributes ` 选项),
33- - ` characterDataOldValue ` - 如果为 ` true ` ,则将 ` node.data ` 的旧值和新值都传递给回调函数 (参见下文),否则只传新值(需要 ` characterData ` 选项)。
32+ - ` attributeOldValue ` —— 如果为 ` true ` ,则将特性的旧值和新值都传递给回调 (参见下文),否则只传新值(需要 ` attributes ` 选项),
33+ - ` characterDataOldValue ` —— 如果为 ` true ` ,则将 ` node.data ` 的旧值和新值都传递给回调 (参见下文),否则只传新值(需要 ` characterData ` 选项)。
3434
35- 发生任何变动后,便执行 “回调” 函数:将变动作为一个 [ MutationRecord] ( https://dom.spec.whatwg.org/#mutationrecord ) 对象列表传入第一个参数,而观察器本身作为第二个参数 。
35+ 然后,在发生任何更改后,将执行 “回调”:更改被作为一个 [ MutationRecord] ( https://dom.spec.whatwg.org/#mutationrecord ) 对象列表传入第一个参数,而观察器自身作为第二个参数 。
3636
3737[ MutationRecord] ( https://dom.spec.whatwg.org/#mutationrecord ) 对象具有以下属性:
3838
39- - ` type ` - 变动类型,可以是以下类型中的一个 :
40- - ` "attributes" ` :属性被修改了
41- - ` "characterData" ` :数据被修改了,用于文本节点。
42- - ` "childList" ` :添加/删除了子元素,
43- - ` target ` - 变动发生在何处 :` "attributes" ` 所在的元素,` "characterData" ` 所在的文本节点,或 ` "childList" ` 变动了的某元素 ,
44- - ` addedNodes/removedNodes ` - 添加/删除的节点
45- - ` previousSibling/nextSibling ` - 添加/删除的节点的前后相邻节点
46- - ` attributeName/attributeNamespace ` - 被更改的属性的名称 /命名空间(用于 XML),
47- - ` oldValue ` - 之前的值,仅用于属性或文本变动 ,如果设置了相应选项 ` attributeOldValue ` /` characterDataOldValue ` 。
39+ - ` type ` —— 变动类型,以下类型之一 :
40+ - ` "attributes" ` :特性被修改了,
41+ - ` "characterData" ` :数据被修改了,用于文本节点,
42+ - ` "childList" ` :添加/删除了子元素。
43+ - ` target ` —— 更改发生在何处 :` "attributes" ` 所在的元素,或 ` "characterData" ` 所在的文本节点,或 ` "childList" ` 变动所在的元素 ,
44+ - ` addedNodes/removedNodes ` —— 添加/删除的节点,
45+ - ` previousSibling/nextSibling ` —— 添加/删除的节点的上一个/下一个兄弟节点,
46+ - ` attributeName/attributeNamespace ` —— 被更改的特性的名称 /命名空间(用于 XML),
47+ - ` oldValue ` —— 之前的值,仅适用于特性或文本更改 ,如果设置了相应选项 ` attributeOldValue ` /` characterDataOldValue ` 。
4848
49- 例如,这里有一个 ` <div> ` ,具有 ` contentEditable ` 属性。该属性用于聚焦和编辑节点 。
49+ 例如,这里有一个 ` <div> ` ,它具有 ` contentEditable ` 特性。该特性使我们可以聚焦和编辑元素 。
5050
5151``` html run
5252<div contentEditable id =" elem" >Click and <b >edit</b >, please</div >
@@ -56,27 +56,27 @@ let observer = new MutationObserver(mutationRecords => {
5656 console .log (mutationRecords); // console.log(the changes)
5757});
5858
59- // 监控属性外的所有变动
59+ // 观察除了特性之外的所有变动
6060observer .observe (elem, {
61- childList: true , // 监控直接子节点
62- subtree: true , // 及其后代节点
63- characterDataOldValue: true // 将旧数据传递给回调函数
61+ childList: true , // 观察直接子节点
62+ subtree: true , // 及其更低的后代节点
63+ characterDataOldValue: true // 将旧的数据传递给回调
6464});
6565 </script >
6666```
6767
68- 现在,如果我们更改 ` <b>edit</b> ` 内的文本,便有了一个变动 :
68+ 如果我们在浏览器中运行上面这段代码,并聚焦到给定的 ` <div> ` 上,然后更改 ` < b>edit</b>` 中的文本, ` console.log ` 将显示一个变动 :
6969
7070``` js
7171mutationRecords = [{
7272 type: " characterData" ,
7373 oldValue: " edit" ,
7474 target: < text node> ,
75- // 其他属性留空
75+ // 其他属性为空
7676}];
7777```
7878
79- 如果我们选择并同时删除 ` <b>edit</b> ` ,就会有多个变动。
79+ 如果我们进行更复杂的编辑操作,例如删除 ` <b>edit</b> ` ,那么变动事件可能会包含多个变动记录:
8080
8181``` js
8282mutationRecords = [{
@@ -85,39 +85,39 @@ mutationRecords = [{
8585 removedNodes: [< b> ],
8686 nextSibling: < text node> ,
8787 previousSibling: < text node>
88- // 其他属性留空
88+ // 其他属性为空
8989}, {
9090 type: " characterData"
9191 target: < text node>
92- // ……变动的详细信息取决于浏览器如何处理此类删除事件
92+ // ...变动的详细信息取决于浏览器如何处理此类删除
9393 // 它可能是将两个相邻的文本节点 "edit " 和 ", please" 合并成一个节点,
94- // 或将它们各自作为单独的文本节点
94+ // 或者可能将它们留在单独的文本节点中
9595}];
9696```
9797
98- 因此,` MutationObserver ` 允许对 DOM 子树中的任何更改做出反应 。
98+ 因此,` MutationObserver ` 允许对 DOM 子树中的任何更改作出反应 。
9999
100100## 用于集成
101101
102- 这在什么时候可能有用 ?
102+ 在什么时候可能有用 ?
103103
104- 想象一下,当您在页面上附加了一个第三方脚本,想在页面上添加一个有用的功能,但也同时增添了一些不必要的功能,比如,显示了广告 ` <div class="ads">Unwanted ads</div> ` 。
104+ 想象一下,你需要添加一个第三方脚本,该脚本不仅包含有用的功能,还会执行一些我们不想要的操作,例如显示广告 ` <div class="ads">Unwanted ads</div> ` 。
105105
106- 当然,第三方脚本不会提供删除方法 。
106+ 当然,第三方脚本没有提供删除它的机制 。
107107
108- 使用 ` MutationObserver ` ,我们可以监测到这类元素何时出现在我们的 DOM 中,然后将其删除,并同时保留有用的功能。当然,该脚本的创建者不会开心的,毕竟您拿走他们有用的东西却把广告删除了 。
108+ 使用 ` MutationObserver ` ,我们可以监测到我们不需要的元素何时出现在我们的 DOM 中,并将其删除 。
109109
110- 还有一些其他情况如,第三方脚本会将某些内容添加到我们的文档中,出现这种情况时,我们希望能监测到,并动态调整大小以适应页面等 。
110+ 还有一些其他情况,例如第三方脚本会将某些内容添加到我们的文档中,并且我们希望检测出这种情况何时发生,以调整页面,动态调整某些内容的大小等 。
111111
112- ` MutationObserver ` 可以轻松处理此问题 。
112+ ` MutationObserver ` 使我们能够实现这种需求 。
113113
114114## 用于架构
115115
116- 从架构的角度来看,` MutationObserver ` 也有不错的适用场景 。
116+ 从架构的角度来看,在某些情况下, ` MutationObserver ` 有不错的作用 。
117117
118- 假设我们在建一个有关编程的网站 。自然地,文章和其他材料中可能包含源代码片段 。
118+ 假设我们正在建立一个有关编程的网站 。自然地,文章和其他材料中可能包含源代码段 。
119119
120- 这些代码片段在 HTML 标记中如下所示 :
120+ 在 HTML 标记(markup)中的此类片段如下所示 :
121121
122122``` html
123123...
@@ -128,29 +128,29 @@ mutationRecords = [{
128128...
129129```
130130
131- 另外,我们还将在网站上使用 JavaScript 高亮显示库,例如 [ Prism.js] ( https://prismjs.com/ ) 。调用 ` Prism.highlightElem(pre) ` 会检查此类 ` pre ` 元素的内容,并在其中添加特殊标记和样式,进行彩色语法高亮显示,类似于您在本页面的示例中看到的那样 。
131+ 另外,我们还将在网站上使用 JavaScript 高亮显示库,例如 [ Prism.js] ( https://prismjs.com/ ) 。调用 ` Prism.highlightElem(pre) ` 会检查此类 ` pre ` 元素的内容,并在其中添加特殊标签(tag)和样式,以进行彩色语法高亮显示,类似于你在本文的示例中看到的那样 。
132132
133- 那什么时候运行该高亮显示方法呢?我们可以在 ` DOMContentLoaded ` 事件触发时运行,或在页尾运行 。到那时,DOM 已加载完毕 ,我们可以搜索元素 ` pre[class*="language"] ` 并调用 ` Prism.highlightElem ` :
133+ 那什么时候运行该高亮显示方法呢?我们可以在 ` DOMContentLoaded ` 事件中或者在页面尾部运行 。到那时,我们的 DOM 已准备就绪 ,我们可以搜索元素 ` pre[class*="language"] ` 并对其调用 ` Prism.highlightElem ` :
134134
135135``` js
136- // 在页面上高亮显示所有代码片段
136+ // 高亮显示页面上的所有代码段
137137document .querySelectorAll (' pre[class*="language"]' ).forEach (Prism .highlightElem );
138138```
139139
140- 到目前为止,一切都很简单,对吧?HTML 中有 ` <pre> ` 代码片段,并高亮显示了 。
140+ 到目前为止,一切都很简单,对吧?HTML 中有 ` <pre> ` 代码段,我们高亮显示它们 。
141141
142- 现在继续 。假设我们要从服务器动态获取资料。我们会在 [ 后续教程 ] ( info:fetch ) 中学习怎么做。当前我们只关心从网络服务器获取 HTML 文章后按需显示 :
142+ 现在让我们继续 。假设我们要从服务器动态获取资料。我们将 [ 在本教程的后续章节 ] ( info:fetch ) 中学习进行此操作的方法。目前,只需要关心我们从网络服务器获取 HTML 文章并按需显示 :
143143
144144``` js
145145let article = /* 从服务器获取新内容 */
146146articleElem .innerHTML = article;
147147```
148148
149- 新的 ` article ` HTML 可能包含代码片段 。我们需要对其调用 ` Prism.highlightElem ` ,否则它们将不会高亮显示 。
149+ 新的 ` article ` HTML 可能包含代码段 。我们需要对其调用 ` Prism.highlightElem ` ,否则它们将不会被高亮显示 。
150150
151- ** 对于动态加载的文章,在何处何时调用 ` Prism.highlightElem ` ?**
151+ ** 对于动态加载的文章,应该在何处何时调用 ` Prism.highlightElem ` ?**
152152
153- 我们可以对加载文章的那段代码调用该方法 ,如下所示:
153+ 我们可以将该调用附加到加载文章的代码中 ,如下所示:
154154
155155``` js
156156let article = /* 从服务器获取新内容 */
@@ -162,38 +162,38 @@ snippets.forEach(Prism.highlightElem);
162162*/ ! *
163163```
164164
165- ……但是,想象一下,代码中有很多地方可以加载内容:文章、测验、 论坛帖子。我们需要在所有地方都进行高亮显示调用吗?那不太好办,也很容易遗漏 。
165+ ……但是,想象一下,代码中有很多地方可以加载内容:文章,测验, 论坛帖子。我们是否需要在每个地方都附加一个高亮显示调用?那不太方便,也很容易忘记 。
166166
167- 而且 ,如果内容是由第三方模块加载的,怎么办 ?例如,我们有一个由他人编写的论坛,可以动态加载内容,我们想在论坛中添加语法高亮显示功能。没有人喜欢再接入第三方脚本 。
167+ 并且 ,如果内容是由第三方模块加载的,该怎么办 ?例如,我们有一个由其他人编写的论坛,该论坛可以动态加载内容,并且我们想为其添加语法高亮显示。没有人喜欢修补第三方脚本 。
168168
169169幸运的是,还有另一种选择。
170170
171- 我们可以使用 ` MutationObserver ` ,它能自动检测何时在页面中插入了代码片段,并高亮显示之 。
171+ 我们可以使用 ` MutationObserver ` 来自动检测何时在页面中插入了代码段,并高亮显示之它们 。
172172
173- 因此,我们在一个地方处理高亮显示功能,不再需要集成 。
173+ 因此,我们在一个地方处理高亮显示功能,从而使我们无需集成它 。
174174
175175### 动态高亮显示示例
176176
177- 这是运行示例 。
177+ 这是一个工作示例 。
178178
179- 如果运行此代码,它将开始监测下面的元素,并高亮显示该元素处出现的任何代码段 :
179+ 如果你运行这段代码,它将开始观察下面的元素,并高亮显示现在此处的所有代码段 :
180180
181181``` js run
182182let observer = new MutationObserver (mutations => {
183183
184184 for (let mutation of mutations) {
185- // 检查新节点,是否有需要高亮的 ?
185+ // 检查新节点,有什么需要高亮显示的吗 ?
186186
187187 for (let node of mutation .addedNodes ) {
188- // 我们只跟踪元素,忽略其他节点 (例如文本节点)
188+ // 我们只跟踪元素,跳过其他节点 (例如文本节点)
189189 if (! (node instanceof HTMLElement )) continue ;
190190
191191 // 检查插入的元素是否为代码段
192192 if (node .matches (' pre[class*="language-"]' )) {
193193 Prism .highlightElement (node);
194194 }
195195
196- // 或在子树的某处可能有一个代码片段 ?
196+ // 或者可能在子树的某个地方有一个代码段 ?
197197 for (let elem of node .querySelectorAll (' pre[class*="language-"]' )) {
198198 Prism .highlightElement (elem);
199199 }
@@ -207,56 +207,60 @@ let demoElem = document.getElementById('highlight-demo');
207207observer .observe (demoElem, {childList: true , subtree: true });
208208```
209209
210- 下面有一个 HTML 元素和 JavaScript,该脚本使用 ` innerHTML ` 动态填充 HTML 元素 。
210+ 下面有一个 HTML 元素,以及使用 ` innerHTML ` 动态填充它的 JavaScript 。
211211
212- 请先运行前面的代码(如前述,监测到该元素),然后运行下面的代码。您将看到 ` MutationObserver ` 如何检测并高亮显示该代码段 。
212+ 请先运行前面那段代码(上面那段,观察元素),然后运行下面这段代码。你将看到 ` MutationObserver ` 是如何检测并高亮显示代码段的 。
213213
214- <p id =" highlight-demo " style =" border : 1px solid #ddd " >A demo-element with <code >id="highlight-demo"</code >, run the code above to observe it. </p >
214+ <p id =" highlight-demo " style =" border : 1px solid #ddd " >一个具有 <code >id="highlight-demo"</code > 的示例元素,运行上面那段代码来观察它。 </p >
215215
216- 以下代码填充了其 ` innerHTML ` 。请先运行上面的代码,它将监测并高亮显示新内容 :
216+ 下面这段代码填充了其 ` innerHTML ` ,这导致 ` MutationObserver ` 作出反应,并突出显示其内容 :
217217
218218``` js run
219219let demoElem = document .getElementById (' highlight-demo' );
220220
221221// 动态插入带有代码段的内容
222- demoElem .innerHTML = ` A code snippet is below:
222+ demoElem .innerHTML = ` 下面是一个代码段:
223223 <pre class =" language-javascript" ><code > let hello = "world!"; </code ></pre >
224- <div >Another one: </div >
224+ <div >另一个代码段: </div >
225225 <div >
226226 <pre class =" language-css" ><code >.class { margin: 5px; } </code ></pre >
227227 </div >
228228` ;
229229```
230230
231- 现在我们有了 ` MutationObserver ` ,它可以跟踪在被监测的元素或整个“文档” 中的所有高亮显示。我们可以在 HTML 中随意添加 /删除代码段。
231+ 现在我们有了 ` MutationObserver ` ,它可以跟踪观察到的元素中的,或者整个 ` document ` 中的所有高亮显示。我们可以在 HTML 中添加 /删除代码段,而无需考虑高亮问题 。
232232
233233## 其他方法
234234
235- 有一个方法可以停止监测节点 :
235+ 有一个方法可以停止观察节点 :
236236
237- - ` observer.disconnect() ` - 停止监测 。
237+ - ` observer.disconnect() ` —— 停止观察 。
238238
239- 跟它经常一起使用的另一个方法:
239+ 当我们停止观察时,观察器可能尚未处理某些更改。
240240
241- - ` mutationRecords = observer.takeRecords() ` - 获取尚未处理的变动记录列表,即发生了变动但回调函数还没有处理它们。
241+ - ` observer.takeRecords() ` —— 获取尚未处理的变动记录列表,表中记录的是已经发生,但回调暂未处理的变动。
242+
243+ 这些方法可以一起使用,如下所示:
242244
243245``` js
244- // 我们想停止监测变动
246+ // 我们想要停止跟踪变动
245247observer .disconnect ();
246248
247- // 它可能尚未处理某些变动
249+ // 处理未处理的变动
248250let mutationRecords = observer .takeRecords ();
249- // 处理 mutationRecords
251+ ...
250252```
251253
252- ## 垃圾回收
254+ ``` smart header="垃圾回收"
255+ 观察器在内部对节点使用弱引用。也就是说:如果一个节点被从 DOM 中删除了,并且该节点变得不可访问,那么它就会被垃圾回收。
253256
254- 观察器在内部对节点使用弱引用。即:如果一个节点从 DOM 中删除了,便访问不到了,那么它就成了要回收的垃圾,观察器不会阻止这种情况。
257+ 观察到 DOM 节点这一事实并不能阻止垃圾回收。
258+ ```
255259
256260## 总结
257261
258- ` MutationObserver ` 可以监测 DOM 的变动如:属性、 添加/删除的元素、 文本内容。
262+ ` MutationObserver ` 可以对 DOM 的变化作出反应:特性(attribute), 添加/删除的元素, 文本内容。
259263
260264我们可以用它来跟踪代码其他部分引入的更改,以及与第三方脚本集成。
261265
262- ` MutationObserver ` 可以监测任何更改。配置选项“监测哪些内容”用于优化监测,节省不必要的回调调用 。
266+ ` MutationObserver ` 可以跟踪任何更改。 ` config ` “要观察的内容”选项用于优化,避免不必要的回调调用以节省资源 。
0 commit comments