变异观察者触发两次
Mutation observer fires twice
我有以下有效的代码,但如果我单击一个面板然后再次单击它,突变观察器会在第二次单击时触发两次。有谁知道这是什么原因以及如何阻止它?
我猜这是因为它在添加 class 时触发一次,在删除 class 时触发一次。但如果是这种情况,当您执行删除 class 时 classList.contains
不应该为真(如果您单击另一个面板可以看到 - 只有那个添加 class 事件火灾)
const activeClass = 'active';
const $panels = $('.panel');
let counter = 0;
$panels.on('click', e => {
const $panel = $(e.currentTarget);
$panels.removeClass(activeClass);
$panel.addClass(activeClass);
});
$panels.each((index, panel) => {
const observer = new MutationObserver(mutationsList => {
for (let i = 0; i < mutationsList.length; i++) {
if (mutationsList[i].attributeName === 'class' && mutationsList[i].target.classList.contains(activeClass)) {
console.log(mutationsList[i].target.className, counter);
counter++;
}
}
});
observer.observe(panel, {
attributes: true,
childList: false,
subtree: false
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="panel panel--1">
panel
</div>
<div class="panel panel--2">
panel
</div>
<div class="panel panel--3">
panel
</div>
无论我们单击哪个面板,当有一个活动面板时,您的代码都会进行两次更改:
- 记录 1:删除 class
- 记录 2:添加 class
您观察到的同一元素点击行为的差异是由处理程序的 if
条件引起的。这可能令人惊讶,但它看到 active
class 出现在第一个突变记录(用于删除)的元素上,因为 MutationObserver 的处理程序作为微任务运行 - 这意味着它在当前任务完成后运行- 意味着 addClass() 已经在同一元素上添加了 class。
作为解决方案,如果单击的元素已经处于活动状态,您可以跳过该工作:
$panels.on('click', e => {
const $panel = $(e.currentTarget);
if (!$panel.hasClass(activeClass)) {
$panels.removeClass(activeClass);
$panel.addClass(activeClass);
}
});
Mutation Observer 是异步的。这意味着它会记录所有更改,并在将完成的更改的完整记录传递给 DOM.
后调用回调函数
点击一次后
当您再次点击时
记录
- class 移除
- class 添加
当前状态:Class added
(调用回调时)
因此,class列表包含 returns true。
经过大量的调试,我发现对于class列表中的每个动作,都会在突变列表中添加一条新的突变记录。但是,突变记录的目标是元素的最终状态,而不是事件发生时元素的状态。
为了解决这个问题,我已经对目标的 class 名称进行了测试,看看它是否已经触发了突变(幸运的是我的元素具有唯一的 classes)以及是否不触发功能。
这可能不是最好的做法,但似乎解决了我的问题,所以如果有人有更好的方法,请随时 post 它
const activeClass = 'active';
const $panels = $('.panel');
$panels.on('click', e => {
const $panel = $(e.currentTarget);
$panel.removeClass(activeClass);
$panel.addClass(activeClass);
});
$panels.each((index, panel) => {
const observer = new MutationObserver(mutationsList => {
const groupedMutations = [];
for (let i = 0; i < mutationsList.length; i++) {
if (mutationsList[i].attributeName === 'class' &&
groupedMutations.indexOf(mutationsList[i].target.className) == -1 &&
mutationsList[i].target.classList.contains(activeClass)) {
groupedMutations.push(mutationsList[i].target.className);
console.log(mutationsList[i].target.className, i)
}
}
});
observer.observe(panel, {
attributes: true,
childList: false,
subtree: false
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="panel panel--1">
panel
</div>
<div class="panel panel--2">
panel
</div>
<div class="panel panel--3">
panel
</div>
我有以下有效的代码,但如果我单击一个面板然后再次单击它,突变观察器会在第二次单击时触发两次。有谁知道这是什么原因以及如何阻止它?
我猜这是因为它在添加 class 时触发一次,在删除 class 时触发一次。但如果是这种情况,当您执行删除 class 时 classList.contains
不应该为真(如果您单击另一个面板可以看到 - 只有那个添加 class 事件火灾)
const activeClass = 'active';
const $panels = $('.panel');
let counter = 0;
$panels.on('click', e => {
const $panel = $(e.currentTarget);
$panels.removeClass(activeClass);
$panel.addClass(activeClass);
});
$panels.each((index, panel) => {
const observer = new MutationObserver(mutationsList => {
for (let i = 0; i < mutationsList.length; i++) {
if (mutationsList[i].attributeName === 'class' && mutationsList[i].target.classList.contains(activeClass)) {
console.log(mutationsList[i].target.className, counter);
counter++;
}
}
});
observer.observe(panel, {
attributes: true,
childList: false,
subtree: false
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="panel panel--1">
panel
</div>
<div class="panel panel--2">
panel
</div>
<div class="panel panel--3">
panel
</div>
无论我们单击哪个面板,当有一个活动面板时,您的代码都会进行两次更改:
- 记录 1:删除 class
- 记录 2:添加 class
您观察到的同一元素点击行为的差异是由处理程序的 if
条件引起的。这可能令人惊讶,但它看到 active
class 出现在第一个突变记录(用于删除)的元素上,因为 MutationObserver 的处理程序作为微任务运行 - 这意味着它在当前任务完成后运行- 意味着 addClass() 已经在同一元素上添加了 class。
作为解决方案,如果单击的元素已经处于活动状态,您可以跳过该工作:
$panels.on('click', e => {
const $panel = $(e.currentTarget);
if (!$panel.hasClass(activeClass)) {
$panels.removeClass(activeClass);
$panel.addClass(activeClass);
}
});
Mutation Observer 是异步的。这意味着它会记录所有更改,并在将完成的更改的完整记录传递给 DOM.
后调用回调函数点击一次后 当您再次点击时
记录
- class 移除
- class 添加
当前状态:Class added
(调用回调时)
因此,class列表包含 returns true。
经过大量的调试,我发现对于class列表中的每个动作,都会在突变列表中添加一条新的突变记录。但是,突变记录的目标是元素的最终状态,而不是事件发生时元素的状态。
为了解决这个问题,我已经对目标的 class 名称进行了测试,看看它是否已经触发了突变(幸运的是我的元素具有唯一的 classes)以及是否不触发功能。
这可能不是最好的做法,但似乎解决了我的问题,所以如果有人有更好的方法,请随时 post 它
const activeClass = 'active';
const $panels = $('.panel');
$panels.on('click', e => {
const $panel = $(e.currentTarget);
$panel.removeClass(activeClass);
$panel.addClass(activeClass);
});
$panels.each((index, panel) => {
const observer = new MutationObserver(mutationsList => {
const groupedMutations = [];
for (let i = 0; i < mutationsList.length; i++) {
if (mutationsList[i].attributeName === 'class' &&
groupedMutations.indexOf(mutationsList[i].target.className) == -1 &&
mutationsList[i].target.classList.contains(activeClass)) {
groupedMutations.push(mutationsList[i].target.className);
console.log(mutationsList[i].target.className, i)
}
}
});
observer.observe(panel, {
attributes: true,
childList: false,
subtree: false
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="panel panel--1">
panel
</div>
<div class="panel panel--2">
panel
</div>
<div class="panel panel--3">
panel
</div>