检查JS中的异步函数重新输入

Din*_*inu 6 javascript mutation-observers

场景:

我们有一个MutationObserver处理函数handler

在中handler,我们进行了一些会handler再次触发的DOM操作。从概念上讲,我们将有一个可重入handler电话。除非MutationObserver未在线程中运行,否则它将在handler完成执行后触发。

因此,handler将触发自身,但通过异步队列,而不是线程内。JS调试器似乎知道这一点,它将在调用堆栈中(即使用Chrome)作为异步祖先出现。

为了对事件进行有效的反跳,我们需要对其进行检测;也就是说,如果handler是由于自身触发的更改而被调用的。

那该怎么办呢?

mutationObserver=new MutationObserver(handler);
mutationObserver.observe(window.document,{
    attributes:true,
    characterData:true,
    childList:true,
    subtree:true
});

var isHandling;
function handler(){
    console.log('handler');

    //  The test below won't work, as the re-entrant call 
    //  is placed out-of-sync, after isHandling has been reset
    if(isHandling){
        console.log('Re-entry!');
        //  Throttle/debounce and completely different handling logic
        return;
    }
    
    isHandling=true;
    
    //  Trigger a MutationObserver change
    setTimeout(function(){
        // The below condition should not be here, I added it just to not clog the 
        // console by avoiding first-level recursion: if we always set class=bar,
        // handler will trigger itself right here indefinitely. But this can be
        // avoided by disabling the MutationObserver while handling.
        if(document.getElementById('foo').getAttribute('class')!='bar'){
            document.getElementById('foo').setAttribute('class','bar');
        }
    },0);
    
    isHandling=false;
}


// NOTE: THE CODE BELOW IS IN THE OBSERVED CONTENT, I CANNOT CHANGE THE CODE BELOW DIRECTLY, THAT'S WHY I USE THE OBSERVER IN THE FIRST PLACE

//  Trigger a MutationObserver change
setTimeout(function(){
  document.getElementById('asd').setAttribute('class','something');
},0);

document.getElementById('foo').addEventListener('webkitTransitionEnd',animend);
document.getElementById('foo').addEventListener('mozTransitionEnd',animend);


function animend(){
    console.log('animend');
    this.setAttribute('class','bar-final');
}
Run Code Online (Sandbox Code Playgroud)
#foo {
    width:0px;
    background:red;
    transition: all 1s;
    height:20px;
}
#foo.bar {
    width:100px;
    transition: width 1s;
}
#foo.bar-final {
    width:200px;
    background:green;
    transition:none;
}
Run Code Online (Sandbox Code Playgroud)
<div id="foo" ontransitionend="animend"></div>
<div id="asd"></div>
Run Code Online (Sandbox Code Playgroud)

注意 我们的用例在这里由2个组件组成;我们将其称为内容,它是任何常规的Web应用程序,具有许多UI组件和界面。还有一个overlay,它是观察内容以进行更改并可能自行进行更改的组件。

一个简单的想法还不够,就是仅禁用MutationObserverwhile处理;或者,假设第二个调用handler是递归的;在上述animationend事件中这种情况下不起作用:内容可以具有处理程序,这些处理程序又可以触发异步操作。两种最流行的这样的问题是:onanimationend/ oneventendonscroll

因此,仅检测直接(首次调用)递归的想法还不够,我们在字面上需要等效于调试器中的调用堆栈视图:一种判断调用(无论以后有多少异步调用)是否是一种方法。本身的后代。

因此,这个问题不仅仅限于MutationObserver,因为它必然涉及一种在调用树中检测其自身后代的异步调用的通用方法。MutationObserver实际上,您可以用任何异步事件代替。

上面示例的解释:在示例中,只要不是,突变观察器就会触发bar动画。但是,内容具有设置为触发恶意的自递归链的处理程序。我们希望通过检测到它是我们自己的操作(从开始动画)来放弃对更改的反应。#foo#foo.bartransitionend#foo.bar-final#foo.bar-final#foo.bar

Tar*_*ani 1

一种可能的解决方法是在触发一个突变时停止突变观察者

mutationObserver=new MutationObserver(handler);
mutationObserver.observe(window.document,{
    attributes:true,
    characterData:true,
    childList:true,
    subtree:true
});

//  Trigger a MutationObserver change
document.getElementById('foo').setAttribute('class','bar');
document.getElementById('foo').setAttribute('class','');

function handler(){
    console.log('Modification happend')

        mutationObserver.disconnect();
    //  Trigger a MutationObserver change
    document.getElementById('foo').setAttribute('class','bar');
    document.getElementById('foo').setAttribute('class','');

    mutationObserver.observe(window.document,{
    attributes:true,
    characterData:true,
    childList:true,
    subtree:true
});
}
Run Code Online (Sandbox Code Playgroud)

查看 JS 小提琴

https://jsfiddle.net/tarunlalwani/8kf6t2oh/2/