Как вы заставляете событие javascript запускаться первым, независимо от порядка добавления событий?

У меня есть javascript, который люди включают в свою страницу. В моем javascript у меня есть версия jQuery (1.8 для удобства использования), которая разделена на собственное пространство имен и на которую ссылается глобальная переменная (но не одна из двух переменных по умолчанию "$" или "jQuery"), Это позволяет пользователям иметь jQuery на своей странице и не мешать тому, что я делаю внутренне в своих функциях.

Итак, у нас есть одна страница, на которой уже есть jQuery (1.4), и все работает нормально, за исключением того, что и мой, и мой код слушают события "щелчка" на элементах, а их происходит первым, поэтому на тех немногих событиях, которые они делают, это происходит. верните false, jQuery останавливает распространение, и мое событие никогда не запускается. Мне нужно, чтобы мое мероприятие прошло первым. Пользователь ожидает, что моя функциональность onClick все еще будет работать.

Теперь я знаю, что jQuery хранит свой собственный порядок событий внутри себя через объект _data(), и с помощью этого можно отменить привязку существующих событий, связать мое событие, а затем повторно связать существующие события, но это относится только к объектам, связанным с этим экземпляром. jQuery. Я предпочел бы не просто слепо искать объект jQuery в надежде, что конфликт был введен собственной версией jQuery пользователя. В конце концов, что происходит, когда пользователь связывает событие не через jQuery? Попытка манипулировать существующим объектом jQuery на странице не является хорошим решением.

Я знаю, что в зависимости от браузера, они используют addEventListener / removeEventListener или attachEvent / detachEvent. Если бы только я мог получить список уже добавленных событий, я мог бы связать их в нужном мне порядке, но я не могу узнать, как. Просматривая DOM через Chrome Inspect, я нигде не вижу привязки по клику (ни на объекте, ни на окне или документе).

У меня самое сложное время, чтобы выяснить, где именно jQuery связывает свое прослушивание. Чтобы иметь возможность контролировать порядок своих собственных событий, jQuery должен где-то прослушивать, а затем запускать свои собственные функции, верно? Если бы я мог выяснить, где это сделано, я мог бы получить представление о том, как обеспечить, чтобы мое мероприятие всегда было первым. Или, может быть, есть какой-то javascript API, который я не смог найти в Google.

Какие-либо предложения?

7 ответов

Решение

Как сказали Берги и Крис Хилд в комментариях, оказывается, что нет никакого способа получить доступ к существующим событиям из DOM, и нет способа вставить события "первым". Они запускаются в том порядке, в каком они были вставлены по дизайну, и скрыты по дизайну. Как упоминалось в нескольких постерах, у вас есть доступ к тем, которые были добавлены через тот же экземпляр jQuery, который вы используете через данные jQuery, но это все.

Есть еще один случай, когда вы можете запустить перед событием, которое было связано до запуска вашего кода, и это если они использовали атрибут HTML "onclick". В этом случае вы можете написать функцию-обертку, на что нет необходимости указывать в комментариях, приведенных ниже. Хотя это не помогло бы в случае первоначального вопроса, который я задал, и в настоящее время очень редко для событий, которые будут связаны таким образом (большинство людей и фреймворки используют addEvent или attachEventListener ниже), это один сценарий, в котором вы можете решить вопрос "беги первым", и так как многие люди посещают этот вопрос в поисках ответов сейчас, я решил убедиться, что ответ завершен.

Мы решили это, просто добавив небольшое расширение 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 */ });

Очень просто!

Я сталкиваюсь с противоположной ситуацией, когда меня попросили включить библиотеку, которая использует 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 - фаза захвата
  • false - пузырьковая фаза

Полезные ссылки: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

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

Можно использовать предложение Алексея Павленко по поводу useCapture . Его утверждение о том, что он должен быть прикреплен к родительскому узлу, неверно: MDN

Слушатели событий на этапе «захвата» вызываются перед прослушивателями событий на любых этапах, не связанных с захватом.

      target.addEventListener(type, listener, useCapture);

Было проще добавить addListener и removeListener методы для document (поскольку это только там, где они мне нужны - я полагаю, вы можете использовать Element.prototype и thisвместо). Для каждого типа добавляется только один "настоящий" слушатель, и это просто функция для вызова реальных слушателей по порядку. В eventListenersсловарь добавлен в документ (так что можно испортить обработчик или заказ).

[править] Я думаю, что в большинстве случаев правильный ответ - использовать третий аргумент addEventListener: /questions/5398638/onclick-prioritet-nad-addeventlistener-v-javascript/5398654#5398654. В приведенном ниже ответе аргумент игнорируется (намеренно).

[править] Обновлен код, чтобы добавить только одно дополнительное свойство: 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 при решении подобных проблем.

Другие вопросы по тегам