Есть ли прослушиватель изменений JavaScript/jQuery DOM?

По существу, я хочу, чтобы script выполнялся при изменении содержимого DIV. Поскольку сценарии являются отдельными (содержимое script в chrome extension и webpage script), мне нужен способ просто наблюдать изменения в состоянии DOM. Я мог настроить опрос, но это кажется неаккуратным.

Ответ 1

Несколько лет спустя в настоящее время официально существует лучшее решение. DOM4 Mutation Observers заменяют устаревшие события мутации DOM3. Они в настоящее время реализованы в современных браузерах как MutationObserver (или как префикс с префиксом поставщика WebKitMutationObserver в старых версиях Chrome):

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function(mutations, observer) {
    // fired when a mutation occurs
    console.log(mutations, observer);
    // ...
});

// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
  subtree: true,
  attributes: true
  //...
});

В этом примере прослушиваются изменения DOM на document и все его поддерево, и он будет активировать изменения атрибутов элемента, а также структурные изменения. В проекте spec есть полный список действительных свойств прослушивателя мутаций:

childList

  • Установите для true, если необходимо наблюдать мутации для целевых детей.

атрибуты

  • Установите для true, если необходимо наблюдать мутации для целевых атрибутов.

CharacterData​​STRONG >

  • Установите для true, если необходимо отслеживать мутации для целевых данных.

поддерево

  • Установите для true, если необходимо учитывать мутации, а не целевые потомки.

attributeOldValue

  • Установите значение true, если для параметра attributes установлено значение true и target, перед тем как мутация должна быть записана.

characterDataOldValue

  • Установите true, если для параметра characterData установлено значение true и целевые данные до того, как необходимо записать мутацию.

attributeFilter

  • Установить список локальных имен атрибутов (без пространства имен), если не все атрибутные мутации необходимо соблюдать.

(Этот список действует с апреля 2014 года, вы можете проверить спецификацию на любые изменения.)

Ответ 2

редактировать

Этот ответ теперь устарел. См. Ответ apsillers.

Поскольку это расширение Chrome, вы можете также использовать стандартное событие DOM - DOMSubtreeModified. См. Поддержку этого события в браузерах. Он поддерживается в Chrome с версии 1.0.

$("#someDiv").bind("DOMSubtreeModified", function() {
    alert("tree changed");
});

См. Рабочий пример здесь.

Ответ 3

Многие сайты используют AJAX для динамического добавления/отображения/изменения контента. Иногда он используется вместо навигации на сайте, поэтому текущий URL-адрес изменяется программно, а сценарии содержимого в этом случае автоматически не выполняются браузером, так как страница не извлекается с удаленного сервера целиком.


Обычные JS-методы обнаружения изменений страницы, доступные в контенте script.


Специфические расширения: обнаружение изменений URL в background/страница событий.

Есть расширенный API для работы с навигацией: webNavigation, webRequest, но мы будем использовать простой chrome.tabs.onUpdated прослушиватель событий, который отправляет сообщение в контент script:

  • manifest.json:
    declare фон/страница событий
    объявить контент script
    add "tabs" permission.

  • background.js

    var rxLookfor = /^https?:\/\/(www\.)?google\.(com|\w\w(\.\w\w)?)\/.*?[?#&]q=/;
    chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
        if (rxLookfor.test(changeInfo.url)) {
            chrome.tabs.sendMessage(tabId, 'url-update');
        }
    });
    
  • content.js

    chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
        if (msg === 'url-update') {
            doSomething();
        }
    });
    

Ответ 4

Другой подход зависит от того, как вы меняете div. Если вы используете JQuery для изменения содержимого div с помощью метода html(), вы можете расширить этот метод и вызвать функцию регистрации каждый раз, когда вы помещаете html в div.

(function( $, oldHtmlMethod ){
    // Override the core html method in the jQuery object.
    $.fn.html = function(){
        // Execute the original HTML method using the
        // augmented arguments collection.

        var results = oldHtmlMethod.apply( this, arguments );
        com.invisibility.elements.findAndRegisterElements(this);
        return results;

    };
})( jQuery, jQuery.fn.html );

Мы просто перехватываем вызовы html(), вызываем функцию регистрации с этим, которая в контексте относится к целевому элементу, получающему новый контент, затем мы передаем вызов исходной функции jquery.html(). Не забудьте вернуть результаты исходного метода html(), потому что JQuery ожидает его для цепочки методов.

Для получения дополнительной информации об переопределении и расширении метода проверьте http://www.bennadel.com/blog/2009-Using-Self-Executing-Function-Arguments-To-Override-Core-jQuery-Methods.htm, где я скрепил функцию закрытия. Также ознакомьтесь с учебником плагинов на сайте JQuery.

Ответ 5

В дополнение к "необработанным" инструментам, предоставляемым MutationObserver API, существуют "удобные" библиотеки для работы с DOM-мутациями.

Рассмотрим: MutationObserver представляет каждое изменение DOM в терминах поддеревьев. Поэтому, если вы, например, ожидаете, что какой-то элемент будет вставлен, он может быть глубоко внутри детей mutations.mutation[i].addedNodes[j].

Другая проблема заключается в том, что ваш собственный код в ответ на мутации изменяет DOM - вы часто хотите отфильтровать его.

Хорошая библиотека удобства, разрешающая такие проблемы, mutation-summary (отказ от ответственности: я не автор, просто довольный пользователь), который позволяет вам указывать запросы о том, что вас интересует, и получить именно это.

Основной пример использования из документов:

var observer = new MutationSummary({
  callback: updateWidgets,
  queries: [{
    element: '[data-widget]'
  }]
});

function updateWidgets(summaries) {
  var widgetSummary = summaries[0];
  widgetSummary.added.forEach(buildNewWidget);
  widgetSummary.removed.forEach(cleanupExistingWidget);
}