本系列其他译文请看JS工作机制 - 小白1991的专栏 - 掘金 (juejin.cn) 本章阅读指数:3
本章内容,适合做一些性能检测或者前端测试方案,可根据自身情况阅读
客户端的Web应用已经越来越重了,这是由于人民群众日益增长的审美需求导致的。客户端需要提供更丰富的交互,实时计算等等。 复杂性的增长,让我们准确的知道运行期间任意时刻UI的状态、 当你构建一个框架或者库,需要通过监测 DOM 来响应并执行一些特定的操作,那就更困难了。
概览
MutationObserver 是现代浏览器提供的WEB API,用来监听DOM的变化。用它可以监听到最新增加/删除的节点,节点属性的改变,以及文本节点中文本的改变。
什么场景下要这么做呢?
- 你想通知用户,他当前的页面已经改变了。
- 使用一些根据DOM变化动态加载JS模块的框架
- 如果是一个WYSIWYG编辑器,要实现undo/redo功能。通过MutationObserver API,你就知道发生了什么改变,然后轻松的undo它们。
看看MutationObserver 是如何起作用的。
怎么使用MutationObserver
在应用中实现MutationObserver是非常简单的。你可以在创建MutationObserver 实例时传递一个函数,这个函数会在每次DOM改变时触发。这个函数的第一个参数,是在一个批次中所有发生变化的DOM集合。每一个改变,包含了它的类型和发生的改变。
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 id="sample-div" class="test"> Simple div </div>
使用jQuery 删除class 属性
$("#sample-div").removeAttr("class");
调用mutationObserver.observe(...) 之后,我们将会看到打印出MutationRecord日志
这次的变化是通过移除
class 属性触发的.
当任务完成之后,如果要停止监听DOM,要这么做
// Stops the MutationObserver from listening for changes.
mutationObserver.disconnect();
如今 MutationObserver 已经被广泛支持了:
替代方案
但是MutationObserver还没有完全推开,在此之前开发者需要怎么做呢
有一些其他的可选方案:
- Polling
- MutationEvents
- CSS animations
Polling (轮询)
最简单暴力的方式是去轮询。使用浏览器的setInterval方法,可以设置一个任务,周期性的检查有什么变化。这个方式自然会影响到app的性能。
MutationEvents
早在2000年,就引入了MutationEvents API 。尽管很有用,但是DOM的每一次改变都会触发mutation事件,这也引入了性能问题。如今MuatationAPI已经被遗弃了,很快所有的浏览器也都会不支持。
MutationEvents支持情况:
CSS 动画
一个相对陌生的替代方案是,是使用CSS animations.
听起来有点诡异。这个方案是创建一个动画,一旦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;
}
这个代码把动画添加到container-element的所有子节点上,当动画结束,会触发插入事件
我们需要一个JS函数,作为事件的监听者。在函数内部,初始化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中可能发生的变化,而且性能更好。最关键的是,几乎所有的浏览器都支持它,同时还有两个基于MutationEvents实现的polyfills