HTML5精通:DOM突变

HTML5 Mastery系列图片

有时,当节点在DOM树中更改时,需要通知您。 在最近的文章中,我们研究了从约束验证到遍历节点的所有内容。 我们始终假设仅在执行时才知道有关特定节点结构的信息。 这种假设在大多数情况下是正确的,但在视图管理器方面可能有缺陷。

视图管理器试图使视图与某些基础数据保持一致,并且还可能希望使视图在另一个方向上保持一致。 如果视图中的某些内容发生更改,则这些更改需要反映在原始数据集中。

在本教程中,我们将了解MutationObserver ,它为我们提供了一种观察DOM树中的更改的方法。 它旨在替代DOM L3事件规范中定义的原始突变事件。

倾听变化

Mutation Events API是在2000年左右指定的。它应该提供一种简单的方法来观察DOM树中的变化并对变化做出反应。 它由几个不同的事件组成,例如DOMNodeRemovedDOMAttrModified 。 节点更改后,事件即直接(即同步)触发。

即使此功能没有得到很大的普及,它还是提供了一种非常方便的观察更改的方法。 浏览器扩展是最流行的用例之一。 如果页面中的某些内容发生更改,则可以通知已安装的扩展。 此时,可以在页面上执行一些工作。

因此,让我们看一个经典的解决方案,用于通知DOM树中的潜在更改。 设置正确的侦听器与以下代码段一样简单。

["DOMNodeInserted", "DOMAttrModified", "DOMNodeRemoved"].forEach(function (eventName) {
	document.documentElement.addEventListener(eventName, callback, true);
});

侦听器的定义如下。 我们打开事件参数的attrChange属性。

var callback = function (ev) {
	var nodeOrAttr = ev.relatedNode;

	switch (ev.attrChange) {
		case MutationEvent.MODIFICATION:
			console.log('changed attribute', ev.attrName, ev.prevValue, ev.newValue, nodeOrAttr);
			break;
		case MutationEvent.ADDITION:
			console.log('added attribute', ev.attrName, ev.prevValue, ev.newValue, nodeOrAttr);
			break;
		case MutationEvent.REMOVAL:
			console.log('removed attribute', ev.attrName, ev.prevValue, ev.newValue, nodeOrAttr);
			break;
		default:
			console.log(ev.type, nodeOrAttr);
			break;
	}
};

上面的代码测试可以在任何HTML页面上执行。 我们使用以下代码查看如何调用我们的侦听器。

var newNode = document.createElement('div');
newNode.setAttribute('id', 'initial');
document.body.appendChild(newNode);
newNode.setAttribute('class', 'foo');
newNode.setAttribute('id', 'bar');
newNode.removeAttribute('id');
document.body.removeChild(newNode);

在Firefox中,我们将看到类似于下图的输出。

Firefox突变事件

不幸的是,在所有浏览器中观察到的行为都不相同。 我们刚刚介绍的方法存在许多细微的问题。 最大的担忧之一是,在大多数流行的浏览器中,尚未以完整且可互操作的方式来实现Mutation事件。

另一个原因是,即使Mutation事件非常有用,但它们始终是导致性能问题的原因。 他们很慢。 部分原因是它们以同步方式过于频繁地发射。 同样,我们可能最终会以递归循环的形式填充堆栈。 最后,它们可能是浏览器中某些不良错误的根源。

我们可以做得更好吗? 我们可以! 现在,我们介​​绍MutationObserver接口。

变异观察者

DOM L4规范中引入的突变观察者将替代以前的突变事件。 有许多主要差异。 对我们来说,最重要的是异步执行。 MutationObserver实例永远不会在当前事件循环周期中触发,而是排队等待在下一个实例循环中运行。 结果,变异观察者聚集了变化。 我们可能没有收到包含所有更改的单个事件,而是收到了许多更改的许多事件。

异步批处理非常有用,因为每次更改DOM时都不会调用我们的观察器方法。 取而代之的是,在所有更改完成之后,将调用回调(很快)。 这避免了同时执行的损失,因为我们在有机会做出反应之前不需要关注未样式化内容的闪烁。

这是回调查找突变观察者的方式:

var callback = function (mutations) {
	mutations.forEach(function (mutation) {
		var target = mutation.target;

		switch (mutation.type) {
			case 'attributes':
				var attribute = mutation.attributeName;
				var oldValue = mutation.oldValue;
				var newValue = target.getAttribute(attribute);

				if (mutation.oldValue === null)
					console.log('added attribute', attribute, '', newValue, target);
				else
					console.log('changed attribute', attribute, oldValue, newValue, target);

				break;
			case 'childList':
				if (mutation.addedNodes.length > 0)
					console.log('added nodes', mutation.addedNodes, target);
				else if (mutation.removedNodes.length > 0)
					console.log('removed nodes', mutation.removedNodes, target);
				break;
		}
	});
};

回调将传递给构造函数以创建一个新的MutationObserver实例。 以下代码段使用了所有可能的选项来开始观察。 实际上,我们可以忽略禁用的选项。

var mo = new MutationObserver(callback);
mo.observe(document.documentElement, {
	childList: true,
	attributes: true,
	characterData: false,
	subtree: true,
	attributeOldValue: true,
	characterDataOldValue: false,

});

显然,使用MutationObserver有很多事情,但是从本质MutationObserver ,它可以归结为:

  1. 创建一个带有回调的新MutationObserver对象,以处理抛出的所有事件。
  2. 告诉MutationObserver对象观察具有所需选项的特定节点。
  3. 通过断开MutationObserver对象的连接来停止观察事件,即
mo.disconnect();

上面的示例使用Mutation事件对与上一个相同的突变做出了反应。 主要区别在于我们使用MutationObserver ,它为我们提供了更好的性能,支持和灵活性。 在Firefox中,我们现在使用MutationObserver获得以下信息。

Firefox突变观察者

即使结果看起来非常相似,也已经明显看出了一个关键的区别:在所有示例中,目标节点都已经被突变。 让我们看一下added attribute事件的行。 在这里,我们添加一个新的class=foo属性。 但是,该属性已经存在。 下一行更加明显。 这是通过将id属性的值从initial更改为bar 。 但是,我们没有机会看到新属性,也没有任何id属性附加到节点。

原因是我们已经(请记住,我们的测试功能是在一个步骤中执行的!)删除了该节点。 MutationObserver调度的异步执行根本不会干扰我们的代码。 因此,结果可能缺少前一种方法的一些交互可能性,但会增加性能和批处理执行。 这些属性值得更多。

实际例子

我们已经提到过,浏览器扩展是需要利用突变观察者的应用程序的典范。 但是,有些框架也离不开它们。 现在,我们要看两个流行的MVC框架,Aurelia和Polymer。

Aurelia是街区的新孩子之一。 它是用于多个平台的最新客户端框架。 它的功能之一是它更喜欢约定而不是配置。 Aurelia团队面临的挑战之一是如何支持IE9之类的浏览器。 他们确定缺少的MutationObserver是其兼容性问题的根本原因。 最终,他们决定用polyfill填充该Kong。

在内部,Aurelia在其模板模块中观察DOM树的修改。 核心框架不关心此类更改。 在模板模块中,我们找到了一个名为ChildObserver的类,该类在ChildObserverBinder实例中使用MutationObserver 。 儿童观察者将这些粘合剂用于每个目标或行为观察。 在资料夹中,我们看到与以下代码段相似的代码。

function bind (source) {
	this.observer.observe(this.target, { childList:true, subtree: true });
	var results = this.target.querySelectorAll(this.selector);

	for (var i = 0; i < results.length; ++i)
		this.behavior[this.property].push(results[i]);
}

function unbind () {
	this.observer.disconnect();
}

在构造函数中创建新的MutationObserver实例后,我们可以使用bind方法从源头实际观察基本target上的更改。 尽管当前不同的源均无效,但已记录观察到的行为。 主要机制可以在上面的代码中找到。 我们获得满足特定选择器的所有节点,并将它们添加到具有指定属性名称的行为列表中。 然后,在发生突变通知的情况下,将使用此列表来确定这些突变是否有趣。

Polymer是广泛使用MutationObserver的另一个框架。 实际上,Aurelia使用的polyfill是由Polymer团队开发的。 现在,可以在webcomponents.js项目中找到大多数Polymer的polyfills

Polymer中MutationObserver的许多用法之一是监视容器的更改。 例如,如果应将样式作用域应用于容器及其所有后代,则变异观察者会监视更改并将相同的样式应用于可能的新元素。

scopeSubtree: function(container, shouldObserve) {
	var scopify = function(node) {
		// ...
	};

	scopify(container);

	if (shouldObserve) {
		var mo = new MutationObserver(function (mxns) {
			mxns.forEach(function (m) {
				if (m.addedNodes) {
					for (var i = 0; i < m.addedNodes.length; i++)
						scopify(m.addedNodes[i]);
				}
			});
		});

		mo.observe(container, { childList: true, subtree: true });
		return mo;
	}
}

上面的示例确保对元素的适当局部样式作用域,这些元素是在此局部范围内创建的,但不受容器的控制。 与第三方库一起使用时,这尤其有用。 当然,本机影子DOM实现将已经可以处理这些情况,而无需采取进一步的措施。

结论

变异观察者是跟踪DOM中变化的一种好方法。 它们提供了一种强大且快速的方式,以便在发生更改时得到通知。 即使对于浏览器扩展和MV *框架的作者来说,它们最有意义,但最好还是详细了解它们。 毕竟,这可能是我们最喜欢的库中的某些内容按其方式工作的原因。

在这里,我们总结了HTML5精通系列。 希望您在此过程中学到一些东西。 当然,并非所有项目都可以应用其中的某些技术,但在其中一个项目中可能会派上用场。 HTML5规范本身就很庞大。 各种扩展和补充材料甚至更大。 整个DOM规范是另一种野兽。

翻译自: https://code.tutsplus.com/tutorials/html5-mastery-dom-mutations--cms-24847

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值