【问题标题】:How to support stopPropagation in delegated event listeners如何在委托事件侦听器中支持 stopPropagation
【发布时间】:2019-07-28 21:12:31
【问题描述】:

我查看了 jQuery 的源代码,以了解它们如何在委派事件侦听器中实现对 event.stopPropagation() 的支持,即 document.on(event, element...),但似乎无法让我自己的 vanilla JS 实现完全工作。

我尝试在我的方法中覆盖原生 event.stopPropagation() 以简单地在事件本身上设置一个 event.propagationStopped,并在决定事件是否应该传播到其父级时寻找它。

当停止传播的事件侦听器附加在其他事件侦听器之前时,这有效,但是当它以相反的顺序附加时它不起作用,这在 jQuery 的实现中按预期工作。

function delegate(type, selector, callback) {
    document.addEventListener(type, function(event) {
        event.stopPropagation = function() {
            event.propagationStopped = true;
        };

        var element = event.target, found;

        while (element && element.parentNode) {
            if (element.matches(selector)) {
                callback.call(element, event);
            }

            if (!event.propagationStopped) {
                element = element.parentNode;
            } else {
                break;
            }
        }
    });
}


delegate('click', '.overlay', function() {
    console.log('Close overlay');
});

delegate('click', '.modal', function(e) {
    e.stopPropagation();

    console.log('Clicked inside modal, stopping propagation...');
});

我希望事件停止在所有事件侦听器中的进一步传播,而不管它们的附加顺序如何,而不是当前的行为。

【问题讨论】:

  • IIRC,jQuery 仅附加一个实际的 DOM 事件侦听器,并将回调存储在元素的数据属性中。然后当 DOM 事件触发时,它会自行完成所有的传播和顺序调用。
  • @Bergi 我也在考虑这个问题,但我希望有一种更简单的方法,因为我不确定如何存储这些(不想将任何东西附加到实际元素),也不知道如何决定它们应该以什么顺序被调用。
  • 我会写一个答案来演示如何做到这一点。不知道有没有更好的办法,我也很好奇……

标签: javascript event-handling stoppropagation event-delegation event-propagation


【解决方案1】:

我认为这只能通过自己在单个事件侦听器中进行委托事件传播来实现。在您当前的实现中,附加到 document 的第二个侦听器总是第二个触发,无论其选择器如何 - 但是当它选择另一个选择器的后代时,您需要先触发它。

在这里,我正在收集委托事件侦听器的列表,然后为您的父遍历中的每个元素对其进行迭代。您可以将列表保留在元素本身上(例如,使用符号或 WeakMap),但由于您只在 document 上委托,我将简单地将它们存储在全局范围内。

const delegatedListeners = {};

function delegate(type, selector, callback) {
    if (!delegatedListeners[type]) {
        const listeners = delegatedListeners[type] = [];
        document.addEventListener(type, function(event) {
            for (let element = event.target; element && element.parentNode; element = element.parentNode) {
                // unfortunately, event.currentTarget cannot be overwritten with element
                for (const {selector, callback} of listeners) {
                    if (element.matches(selector)) {
                        callback.call(element, event);
                    }
                }
                if (event.cancelBubble) { // a getter for the propagation stopped flag :-)
                    break;
                }
            }
        });
    }
    delegatedListeners[type].push({selector, callback});
}

【讨论】:

  • 我不知道只用这么多代码就可以解决它,而且我什至不需要实现stopPropagation,如果只是检查本机cancelBubble属性!我看到你添加了一个 element 参数,我认为这是一个错误,因为当我删除它时,代码开始工作(你也忘记了 for ... of 循环中的左大括号)。它真的很棒,非常感谢。我还决定将 listeners 对象添加为 delegate 本身的子属性,因此我只能将 delegate 导出为 es6 模块。 See JSFiddle
  • @Tidsoptimisten 对于这些明显的错误,请随意suggest an edit,谢谢(在我意识到你只使用document之前,我首先想到了一个更复杂的解决方案,在任意element上安装监听器) .顺便说一句,您仍然可以 export function delegate 而不使侦听器成为属性,将 const delegatedListeners 变量隐藏在模块范围内。
  • 啊,我不知道我能做到,谢谢!是的,我得到了能够将其范围限定为某些元素的想法,以后可能会考虑这样做。最后,这很酷,我既没有意识到我可以像这样导出函数,也没有意识到导出的函数显然仍然可以访问这些常量。
猜你喜欢
  • 1970-01-01
  • 2014-12-15
  • 2020-08-27
  • 1970-01-01
  • 2015-12-11
  • 2016-09-05
  • 1970-01-01
  • 2011-10-14
  • 1970-01-01
相关资源
最近更新 更多