Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10.JavaScript是如何工作的:使用 MutationObserver 跟踪 DOM 的变化 #10

Open
husky-dot opened this issue May 16, 2019 · 0 comments

Comments

@husky-dot
Copy link
Owner

image

Web 应用程序在客户端变得越来越重,原因很多,例如需要更丰富的 UI 来容纳更复杂的应用程序提供的内容,实时计算等等。复杂性的增加使得在 Web 应用程序生命周期的每个给定时刻都很难知道 UI 的确切状态。

而当你在搭建某些框架或者库的时候,甚至会更加困难,例如,前者需要根据 DOM 来作出反应并执行特定的动作。

概述

Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。

概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发。

这样设计是为了应付 DOM 变动频繁的特点。举例来说,如果文档中连续插入1000个 <li>元素,就会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 Mutation Observer 完全不同,只在 1000 个段落都插入结束后才会触发,而且只触发一次。

Mutation Observer有以下特点:

  • 它等待所有脚本任务完成后,才会运行,即采用异步方式

  • 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条地个别处理 DOM 变动

  • 它即可以观察发生在 DOM 节点的所有变动,也可以观察某一类变动

为什么要要监听 DOM?

在很多情况下,MutationObserver API 都可以派上用场。例如:

  • 你希望通知 Web 应用程序访问者,他当前所在的页面发生了一些更改。

  • 你正在开发一个新的 JavaScript 框架,需要根据 DOM 的变化动态加载 JavaScript 模块。

  • 也许你正在开发一个所见即所得(WYSIWYG) 编辑器,试图实现撤消/重做功能。通过利用 MutationObserver API,你可以知道在任何给定的点上进行了哪些更改,因此可以轻松地撤消这些更改。

image

这些只是 MutationObserver 可以提供帮助的几个例子。

MutationObserver 用法

在应用程序中实现 MutationObserver 相当简单。你需要通过传入一个函数来创建一个 MutationObserver 实例,每当有变化发生,这个函数将会被调用。函数的第一个参数是变动数组,每个变化都会提供它的类型和已经发生的变化的信息。

var mutationObserver = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation);
  });
});

这个被创建的对象有三个方法:

  • observe  — 启动监听
  • disconnect — 用来停止观察
  • takeRecords — 返用来清除变动记录,即不再处理未处理的变动。

observe()

observe 方法用来启动监听,它接受两个参数。

  1. 第一个参数:所要观察的 DOM 节点
  2. 第二个参数:一个配置对象,指定所要观察的特定变

下面的片段展示了如何开始启动监听(observe  ):

// 开始侦听页面的根 HTML 元素中的更改。
mutationObserver.observe(document.documentElement, {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true
});

现在,假设 DOM 中有一些非常简单的 div:

<div id="sample-div" class="test"> Simple div </div>

使用 JQuery 来移除这个 div 上的 class:

$("#sample-div").removeAttr("class");

正如我们已经开始观察到的,在调用 mutationObserver.observe(…) 之后,将在控制台中看到相应 MutationRecord 的日志:

image

这个是由移除 class 属性导致的变化。

MutationRecord 对象包含了DOM的相关信息,有如下属性:

**type:**观察的变动类型(attribute、characterData或者childList)
**target:**发生变动的 DOM 节点
**addedNodes:**新增的 DOM 节点
**removedNodes:**删除的 DOM 节点
**previousSibling:**前一个同级节点,如果没有则返回 null
**nextSibling:**下一个同级节点,如果没有则返回 null
**attributeName:**发生变动的属性。如果设置了attributeFilter,则只返回预先指定的属性。
**oldValue:**变动前的值。这个属性只对 attribute 和 characterData 变动有效,如果发生 childList 变动,则返回 null

最后,为了在任务完成后停止观察 DOM,可以执行以下操作:

mutationObserver.disconnect();

现在,MutationObserver 已经被广泛支持:

image

备择方案

MutationObserver 在之前还没有的,那么在 MutationObserver 还没出现之前,开发者采用什么方案呢?

这是几个可用的其他选项:

  • 轮询(Polling)

  • MutationEvents

  • CSS animations

轮询(Polling)

最简单和最简单的方法是轮询。使用浏览器 setInterval 方法,可以设置一个任务,定期检查是否发生了任何更改。当然,这种方法会显著降低web 应用程序/网站的性能。

MutationEvents

在2000年,MutationEvents API 被引入。虽然很有用,但在 DOM中 的每一次更改都会触发改变事件,这同样会导致性能问题。现在 MutationEvents API 已经被弃用,很快现代浏览器将完全停止支持它。

image

CSS animations

另一个有点奇怪的选择是依赖 CSS 动画。这听起来可能有点令人困惑。基本上,我们的想法是创建一个动画,一旦元素被添加到 DOM 中,动画就会被触发。动画开始的那一刻,animationstart 事件将被触发:如果已经将事件处理程序附加到该事件,那么你将确切地知道元素何时被添加到 DOM 中。动画的执行时间周期应该很小,用户几乎看不到它。

首先,需要一个父级元素,我们在它的内部监听节点的插入:

<div id=”container-element”></div>

为了得到节点插入的处理器,需要设置一系列的 keyframe 动画,当节点插入的时候,动画将会开始。

@keyframes nodeInserted { 
 from { opacity: 0.99; }
 to { opacity: 1; } 
}

创建 keyfram 后,还需要把它放入你想监听的元素上,注意应设置很小的 duration 值 —— 它们将会减弱动画在浏览器上留下的痕迹。

#container-element * {
  animation-duration: 0.001s;
  animation-name: nodeInserted;
}

这会将动画添加到 container-element 的所有子节点。 动画结束时,将触发插入事件。

我们需要一个 JavaScript 函数作为事件监听器。在函数中,必须进行初始的 event.animationName 检查以确保它是我们想要的动画。

var insertionListener = function(event) {
  // Making sure that this is the animation we want.
  if (event.animationName === "nodeInserted") {
    console.log("Node has been inserted: " + event.target);
  }
}

现在是时候为父级元素添加事件监听了:

document.addEventListener(“animationstart”, insertionListener, false); // standard + firefox
document.addEventListener(“MSAnimationStart”, insertionListener, false); // IE
document.addEventListener(“webkitAnimationStart”, insertionListener, false); // Chrome + Safari

浏览器对CSS动画的支持情况:

image

MutationObserver 比上述解决方案有许多优点。本质上,它涵盖了 DOM 中可能发生的每一个更改,并且在批量触发更改时,它的优化程度更高。最重要的是,所有主要的现代浏览器都支持 MutationObserver,还有一些使用引擎下 MutationEvents 的 polyfill。

原文:

https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

你的点赞是我持续分享好东西的动力,欢迎点赞!

####欢迎加入前端大家庭,里面会经常分享一些技术资源。

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant