无论事件添加的顺序如何,如何强制Javascript事件先运行?

How do you force your Javascript event to run first, regardless of the order in which the events were added?

本文关键字:事件 Javascript 何强制 运行 添加 顺序      更新时间:2023-09-26

我有Javascript,人们包括在他们的页面。在我的Javascript中,我有一个版本的jQuery(为了方便参考,是1.8),它被分割到自己的命名空间中,并通过一个全局变量(但不是"$"的两个默认变量之一)引用。或"jQuery"。这允许用户在他们的页面中使用jQuery,并且不会干扰我在函数中内部做的事情。

所以我们有一个页面已经有jQuery(1.4),一切都很好,除了用户和我的代码都在听"点击"元素上的事件,而它们的事件首先发生,所以对于它们所做的少数返回false的事件,jQuery停止传播,我的事件永远不会被触发。我得先办我的活动。用户希望我的onClick功能仍然工作。

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

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

我正在努力弄清楚jQuery绑定侦听的确切位置。为了能够控制自己事件的顺序,jQuery必须在某处监听,然后触发自己的函数,对吗?如果我能弄清楚这是在哪里发生的,我可能会对如何确保我的活动总是排在第一位有所了解。或者可能有一些Javascript API我没能在谷歌上找到。

有什么建议吗?

我们通过添加一个小的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 */ });

容易peasy !

正如Bergi和Chris Heald在评论中所说,事实证明没有办法从DOM中获得现有的事件,也没有方法"首先"插入事件。它们是按照设计插入的顺序触发的,也是按照设计隐藏的。正如一些海报所提到的,你可以通过jQuery的数据访问通过jQuery的相同实例添加的内容,但仅此而已。

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

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

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

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

这不是一个合适的解决方案,但是…可以在捕获阶段向父节点添加事件处理程序。而不是目标元素本身!
<div>
    <div id="target"></div>
</div>
target.parentNode.addEventListener('click',()=>{console.log('parent capture phase handler')},true)

addEventListener的第三个参数表示:

  • true -捕获阶段
  • 假气泡相

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

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

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

"捕获"阶段的事件侦听器在任何非捕获阶段的事件侦听器之前被调用

target.addEventListener(type, listener, useCapture);

发现最容易将addListenerremoveListener方法添加到document(因为这只是我需要它们的地方-我想你可以使用Element.prototypethis代替)。只有一个"真正的"。每个类型都添加侦听器,它只是一个按顺序调用实际侦听器的函数。eventListeners字典被添加到文档(因此可以与处理程序或顺序混淆)。

[编辑]我认为大多数情况下的正确答案是使用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;
}

正如所说,我认为如果您覆盖这些函数的本机实现,这可能是可能的。当开发一个库来替代现有实现时,这是一个非常糟糕的做法,因为它很容易与其他库发生冲突。

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

// 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在解决此类问题时提供的灵活性。