【问题标题】:How do you force your Javascript event to run first, regardless of the order in which the events were added?无论添加事件的顺序如何,如何强制您的 Javascript 事件首先运行?
【发布时间】:2012-12-20 20:26:56
【问题描述】:

我有人们在他们的页面中包含的 Javascript。在我的 Javascript 中,我有一个 jQuery 版本(1.8 以便于参考),它被划分到自己的命名空间中,并通过全局变量引用(但不是“$”或“jQuery”的两个默认变量之一) .这允许用户在他们的页面中使用 jQuery,并且不会干扰我在函数内部执行的操作。

所以我们有一个已经有 jQuery (1.4) 的页面,一切正常,除了用户和我的代码都在监听元素上的“点击”事件,而且他们的事件是第一个,所以在少数事件上他们这样做返回 false,jQuery 停止传播,我的事件永远不会被触发。我需要我的活动先进行。用户希望我的 onClick 功能仍然有效。

现在我知道 jQuery 通过 _data() 对象在内部保持自己的事件顺序,通过它可以取消绑定现有事件,绑定我的事件,然后重新绑定现有事件,但这仅适用于绑定的对象通过那个jQuery实例。我宁愿不要盲目地寻找 jQuery 对象,希望冲突是由用户自己的 jQuery 版本引入的。毕竟当用户不通过 jQuery 绑定事件时会发生什么?试图操作页面中现有的 jQuery 对象并不是一个好的解决方案。

我知道,根据浏览器的不同,他们使用addEventListener/removeEventListenerattachEvent/detachEvent。如果我能获得已添加事件的列表,我可以按照我想要的顺序重新绑定它们,但我不知道如何。通过 Chrome 检查查看 DOM,我看不到 onclick 绑定在任何地方(不在对象上,也不在窗口或文档上)。

我正忙着弄清楚 jQuery 究竟在哪里绑定了它的监听。为了能够控制自己事件的顺序,jQuery 必须在某个地方全神贯注地监听,然后触发自己的函数,对吗?如果我能弄清楚这是在哪里完成的,我可能会深入了解如何确保我的活动始终是第一位的。或者也许有一些我在 Google 上找不到的 Javascript API。

有什么建议吗?

【问题讨论】:

标签: javascript jquery dom-events


【解决方案1】:

我们通过添加一个小的 jQuery 扩展来解决这个问题,该扩展在事件链的头部插入事件:

$.fn.bindFirst = function(name, fn) {
  var elem, handlers, i, _len;
  this.bind(name, fn);
  for (i = 0, _len = this.length; i < _len; i++) {
    elem = this[i];
    handlers = jQuery._data(elem).events[name.split('.')[0]];
    handlers.unshift(handlers.pop());
  }
};

然后,绑定你的事件:

$(".foo").bindFirst("click", function() { /* Your handler */ });

简单易懂!

【讨论】:

  • 虽然是一个有用的功能,但我认为这只会在使用我的 jQuery 版本(1.8)排队的事件的开头插入我的事件,但在页面其他地方添加的事件不是通过我的jQuery 仍然可以优先。
  • 嗯,我错过了。这可能是正确的。您是否有理由在同一页面中使用多个并发版本的 jQuery?不过,据我所知,您无法直接从浏览器获取绑定事件列表。
  • 是的,我们的代码是一个 JavaScript 库,可以在有或没有 jQuery 的页面上运行。他们的网站只包含我们的代码,而且他们自己也运行 jQuery。我认为“无法从浏览器获取绑定事件列表”可能只是解决此问题的方法。我能想到的唯一另一件事是尽可能早地在页面中包装 addEventListener/attachEvent 函数,这会变得混乱。
【解决方案2】:

正如 Bergi 和 Chris Heald 在 cmets 中所说,事实证明无法从 DOM 中获取现有事件,也无法“首先”插入事件。它们按照它们被设计插入的顺序被发射,并被设计隐藏。正如一些海报所提到的,您可以访问通过您通过 jQuery 数据使用的同一 jQuery 实例添加的那些,但仅此而已。

还有另一种情况,您可以在代码运行之前绑定的事件之前运行,那就是如果他们使用“onclick”HTML 属性。在这种情况下,您可以编写一个包装函数,正如没有必要在下面的一个相当夸张的评论中指出的那样。虽然这对我问的原始问题的实例没有帮助,而且现在非常很少以这种方式绑定事件(现在大多数人和框架都在下面使用 addEvent 或 attachEventListener),但它是一个场景,你可以解决“先跑”的问题,由于现在很多人访问这个问题寻找答案,我想我会确保答案是完整的。

【讨论】:

  • fiddle: jsfiddle.net/uxdhzfz1 ... 并将 jQuery 添加到组合中,增加 Chris Heald 的回答..
  • 我在此线程的另一条评论中对此进行了讨论,但我想我会将其移至问题中:虽然我也同意我不想覆盖这些函数的默认实现,它仍然不能解决原来的问题。如果我可以尽早运行以在其他人调用 addEventListener 之前运行,那么我可以在那时调用 addEventListener 并在稍后添加的侦听器之前运行,而不是覆盖默认实现。
【解决方案3】:

我遇到了相反的情况,我被要求在我们的网站中包含一个在元素上使用 event.stopImmediatePropagation() 的库。所以我的一些事件处理程序被跳过了。这就是我所做的(如回答here):

<span onclick="yourEventHandler(event)">Button</span>

警告:这不是推荐的绑定事件的方式,其他开发者可能会因此而谋杀你。

【讨论】:

  • 只有在注册其他事件之前将“onclick”添加到元素中才是正确的。但是如果你稍后添加“onclick”它将最后执行
【解决方案4】:

这不是一个合适的解决方案,但是......您可以在捕获阶段将事件处理程序添加到父节点。不在目标元素本身!

<div>
    <div id="target"></div>
</div>

target.parentNode.addEventListener('click',()=>{console.log('parent capture phase handler')},true)

addEventListener 中的第三个参数表示:

  • true - 捕获阶段
  • false - 气泡阶段

有用的链接: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

https://javascript.info/bubbling-and-capturing

【讨论】:

  • Terje Rosenlund 的解释。不能保证目标节点上已经没有处于捕获阶段的侦听器。并且要确保您的侦听器在目标节点上的所有侦听器之前执行 - 您必须将其附加到父节点(在捕获阶段)。
【解决方案5】:

发现将addListenerremoveListener 方法添加到document 是最简单的方法(因为这只是我需要它们的地方——我想你可以使用Element.prototypethis 代替)。每种类型只添加一个“真正的”监听器,它只是一个按顺序调用实际监听器的函数。 eventListeners 字典被添加到 document 中(因此可能会弄乱处理程序或订单)。

[编辑] 我认为大多数情况下的正确答案是使用addEventListener 的第三个参数:https://stackoverflow.com/a/29923421。下面的答案忽略了论点(故意)。

[edit] 更新代码只添加 一个 额外属性:document.eventHandlers + 修改命名。

// Storage.
document.eventListeners             = {};   // { type: [ handlerFunc, listenerFuncs ] }

// Add event listener - returns index.
document.addListener                = (type, listener, atIndex) => {
    // Get info.
    const listening                 = document.eventListeners[type];
    // Add to existing.
    if (listening) {
        // Clean up.
        atIndex                     = atIndex || 0;
        const listeners             = listening[1];     // Array of funcs.
        // Already has.
        const iExists               = listeners.indexOf(listener);
        if (iExists !== -1) {
            // Nothing to do.
            if (iExists === atIndex)
                return              atIndex;
            // Remove from old position.
            listeners.splice(atIndex, 1);
        }
        // Add (supporting one cycle of negatives).
        const nListeners            = listeners.length;
        if (atIndex > nListeners)
            atIndex                 = nListeners;
        else if (atIndex < 0)
            atIndex                 = Math.max(0, atIndex + nListeners + 1);
        listeners.splice(atIndex, 0, listener);
    }
    // New one.
    else {
        // Handler func.
        const handler               = (...args) => {
            const listening         = document.eventListeners[type];
            if (listening) {
                const listeners     = listening[1];     // Array of funcs.
                for (const listener of listeners)
                    listener(...args);
            }
        };
        // Update dictionary.
        document.eventListeners[type]   = [ handler, [ listener ] ];
        // Add listener.
        document.addEventListener(type, handler);
        // First one.
        atIndex                     = 0;
    }
    // Return index.
    return                          atIndex;
};

// Remove event listener - returns index (-1 if not found).
document.removeListener             = (type, listener) => {
    // Get info.
    const listening                 = document.eventListeners[type];
    if (!listening)
        return                      -1;
    // Check if exists.
    const listeners                 = listening[1];
    const iExists                   = listeners.indexOf(listener);
    if (iExists !== -1) {
        // Remove listener.
        listeners.splice(iExists, 1);
        // If last one.
        if (!listeners.length) {
            // Remove listener.
            const handlerFunc       = listening[0];
            document.removeEventListener(type, handlerFunc);
            // Update dictionary.
            delete                  document.eventListeners[type];
        }
    }
    // Return index.
    return                          iExists;
}

【讨论】:

  • 我认为格式可以更容易地从更远的地方(作为一个整体)查看代码。受字典声明和数学方程式的启发;)不幸的是,仅在某些编辑器中效果很好(尤其是在 SublimeText 中) .
【解决方案6】:

可以使用 Aliaksei Pavlenkos 关于 useCapture 的建议。他声称它必须附加到父节点是错误的:MDN

“捕获”阶段的事件监听器在任何非捕获阶段的事件监听器之前被调用

target.addEventListener(type, listener, useCapture);

【讨论】:

    【解决方案7】:

    据说,如果您覆盖这些函数的本机实现,我认为这可能是可能的。这是不好的做法 - 在开发库以更改本机实现时非常不好的做法,因为它很容易与其他库发生冲突。

    但是,为了完整起见,这里有一种可能性(完全未经测试,仅展示一般概念):

    // override createElement()
    var temp = document.createElement;
    document.createElement = function() {
        // create element
        var el = document.createElement.original.apply(document, arguments);
    
        // override addEventListener()
        el.addEventListenerOriginal = el.addEventListener;
        el._my_stored_events = [];
    
        // add custom functions
        el.addEventListener = addEventListenerCustom;
        el.addEventListenerFirst = addEventListenerFirst;
        // ...
    };
    document.createElement.original = temp;
    
    // define main event listeners
    function myMainEventListeners(type) {
        if (myMainEventListeners.all[type] === undefined) {
            myMainEventListeners.all[type] = function() {
                for (var i = 0; i < this._my_stored_events.length; i++) {
                    var event = this._my_stored_events[i];
                    if (event.type == type) {
                        event.listener.apply(this, arguments);
                    }
                }
            }
        }
        return myMainEventListeners.all[type];
    }
    myMainEventListeners.all = {};
    
    // define functions to mess with the event list
    function addEventListenerCustom(type, listener, useCapture, wantsUntrusted) {
        // register handler in personal storage list
        this._my_stored_events.push({
            'type' : type,
            'listener' : listener
        });
    
        // register custom event handler
        if (this.type === undefined) {
            this.type = myMainEventListeners(type);
        }
    }
    
    function addEventListenerFirst(type, listener) {
        // register handler in personal storage list
        this._my_stored_events.push({
            'type' : type,
            'listener' : listener
        });
    
        // register custom event handler
        if (this.type === undefined) {
            this.type = myMainEventListeners(type);
        }
    }
    
    // ...
    

    在这方面需要做更多的工作才能真正锁定它,而且最好不要修改原生库。但这是一个有用的心理练习,有助于展示 JavaScript 在解决此类问题时提供的灵活性。

    【讨论】:

    • 虽然我也同意我不想覆盖这些函数的默认实现,但它仍然无法解决最初的问题。如果我能跑得足够早,在其他人调用 addEventListener 之前运行,那么我可以直接调用 addEventListener,然后在后来添加的侦听器之前运行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-08
    • 2022-07-20
    • 2017-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多