JavaScript工作原理(九):使用MutationObserver跟踪DOM的改变
Web应用程序在客户端越来越重要,原因很多,比如需要更丰富的用户界面来容纳更复杂的应用程序必须提供的功能,实时计算等等。
复杂性的增加使得在Web应用程序的生命周期中的每个特定时刻都很难知道UI的确切状态。
如果你正在构建某种框架或仅仅是一个库,这会变得更加困难,例如,它必须作出反应并执行某些依赖于DOM的操作。
概观
MutationObserver是现代浏览器提供的Web API,用于检测DOM中的变化。使用此API,可以侦听新添加或删除的节点,属性更改或文本节点文本内容的更改。
你为什么想这么做?
MutationObserver API在很多情况下可以非常方便地使用。例如:
- 您希望通知您的网络应用访问者在他当前所在的页面上发生了一些变化。
- 您正在研究一种新的花式JavaScript框架,该框架可根据DOM如何变化来动态加载JavaScript模块。
- 您可能正在使用所见即所得的编辑器,试图实现撤销/重做功能。通过利用MutationObserver API,你可以在任何时候都能很容易undo他们
这些只是MutationObserver如何提供帮助的几个例子。
如何使用MutationObserver
在你的应用中实现MutationObserver相当容易。您需要通过传递一个函数来创建一个MutationObserver实例,该函数在每次发生突变时都会被调用。函数的第一个参数是在一个批次中发生的所有改变的集合。每个改变都提供有关其类型和已发生变化的信息。
MutationObserver API,您可以在任何给定的位置知道进行了哪些更改,因此您可以轻松地撤消它们。
var mutationObserver = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { console.log(mutation); }); });
创建的对象有三个方法:
- observe - 开始倾听变化。需要两个参数 - 您要观察的DOM节点和一个设置对象
- disconnect - 停止监听更改
- takeRecords - 在回调被触发前返回最后一批更改。
以下片段显示了如何开始观察:
// Starts listening for changes in the root HTML element of the page. mutationObserver.observe(document.documentElement, { attributes: true, characterData: true, childList: true, subtree: true, attributeOldValue: true, characterDataOldValue: true });
现在假设你有一些简单的DIV在DOM中:
<div id="sample-div" class="test"> Simple div </div>
使用jQuery,你你可以移除div的class属性:
$("#sample-div").removeAttr("class");
正如我们开始观察的那样,在调用mutationObserver.observe(...)之后,我们将在相应的MutationRecord的控制台中看到一个日志:
这是由删除class属性引起的变异。
最后,为了在完成后停止观察DOM,可以执行以下操作:
// Stops the MutationObserver from listening for changes. mutationObserver.disconnect();
如今,MutationObserver得到了广泛的支持:
备择方案
然而,MutationObserver并不总是可用的。那么在MutationObserver出现之前开发者会采取什么措施呢?
还有其他几个选项可用:
- 轮询
- MutationEvents
- CSS动画
轮询
最简单和最简单的方式是通过轮询。使用浏览器setInterval WebAPI,您可以设置一个任务,定期检查是否发生了任何更改。当然,这种方法会显着降低网络应用程序/网站的性能。
MutationEvents
在2000年,引入了MutationEvents API。虽然有用,但DOM中的每一次更改都会触发突变事件,这又会导致性能问题。现在,MutationEvents API已被弃用,很快,现代浏览器将不再支持它。
这是MutationEvents的浏览器支持:
CSS动画
一个有点奇怪的选择是依赖CSS动画。这听起来有点混乱。基本上,这个想法是创建一个动画,一旦一个元素被添加到DOM就会触发该动画。动画开始的那一刻,animationstart事件将被触发:如果您已将事件处理程序附加到该事件,则您将确切知道元素何时添加到DOM。 动画的执行时间应该很小,以至于用户几乎看不到它。
首先,我们需要一个父元素,在其中我们想要听节点插入:
<div id=”container-element”></div>
为了获得节点插入的句柄,我们需要设置一系列关键帧动画,这些动画将在插入节点时开始:
@keyframes nodeInserted { from { opacity: 0.99; } to { opacity: 1; } }
在创建关键帧后,需要将动画应用到您想要的元素上。请注意,持续时间较短 - 他们正在放松浏览器中的动画脚印:
#container-element * { animation-duration: 0.001s; animation-name: nodeInserted; }
这将动画添加到容器元素的所有子节点。当动画结束时,插入事件将触发。
我们需要一个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动画的浏览器支持:
与上述解决方案相比,MutationObserver具有许多优点。 从本质上讲,它涵盖了DOM中可能发生的每一个变化,并且它在批处理中引发更改时更加优化。最重要的是,所有主要的现代浏览器都支持MutationObserver,以及一些使用MutationEvents的polyfill。
相关推荐
Vue和React是数据驱动视图,如何有效控制DOM操作?能不能把计算,更多的转移为js计算?因为js执行速度很快。patch函数-->patch,对比tag,对比tag与key,对比children