Продуктивність MutationObserver для виявлення вузлів у всій DOM


86

Я зацікавлений у використанні MutationObserverдля виявлення, чи додано певний елемент HTML де-небудь на сторінці HTML. Для прикладу, я скажу, що хочу виявити, чи <li>є де-небудь додані де-небудь у DOM.

Усі MutationObserverприклади, які я бачив досі, виявляють лише додавання вузла до певного контейнера. Наприклад:

трохи HTML

<body>

  ...

  <ul id='my-list'></ul>

  ...

</body>

MutationObserver визначення

var container = document.querySelector('ul#my-list');

var observer = new MutationObserver(function(mutations){
  // Do something here
});

observer.observe(container, {
  childList: true,
  attributes: true,
  characterData: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true
});

Отже, у цьому прикладі програма MutationObserverналаштована на перегляд певного контейнера ( ul#my-list), щоб побачити, чи <li>до нього додано якийсь .

Це проблема, якщо я хотів би бути менш конкретним , і стежити за <li>всіма телами HTML, як це:

var container = document.querySelector('body');

Я знаю, що це працює в основних прикладах, які я налаштував для себе ... Але чи не рекомендується це робити? Це призведе до поганої роботи? І якщо так, то як я можу виявити та виміряти цю проблему продуктивності?

Я зрозумів, що, можливо, була причина, що всі MutationObserverприклади настільки конкретні з цільовим контейнером ... але я не впевнений.


Проблеми з продуктивністю стосуються конкретного сценарію. Якщо весь ваш документ містить лише кілька простих елементів, я впевнений, що у вас не виникне жодних проблем. Якщо ви боїтеся мати проблеми з продуктивністю, профайлюйте
Amit

8
Я використовував багато MutationObservers і змушував їх рекурсивно переглядати весь DOM. Я особисто ніколи не мав проблем із продуктивністю.
Лука

7
Основною причиною введення MutationObservers та припинення дії MutationEvents є те, що MutationObservers набагато швидші, оскільки вони агрегують зміни разом. Ми також використовуємо MutationObservers subtree: trueдля великих документів, і це ніколи не було проблемою.
loganfsmyth

1
Чому ви спостерігаєте за змінами атрибутів та даних символів? Ви самі так говорите - хочете спостерігати за можливими доповненнями liелементів? Якщо щось є кандидатом на неоптимальну ефективність, я б сказав, що просять більше подій, ніж вам потрібно, це це.
amn

Як приклад, Boomerang.js ( github.com/akamai/boomerang ), бібліотека веб-моніторингу продуктивності, використовує в MutationObserverцілому документі для вимірювання часу завантаження сторінки SPA.
JulienD

Відповіді:


185

Ця відповідь насамперед стосується великих та складних сторінок.

Якщо його приєднати перед завантаженням / візуалізацією сторінки, неоптимізований зворотний виклик MutationObserver може додати кілька секунд до часу завантаження сторінки (скажімо, від 5 секунд до 7 секунд), якщо сторінка велика і складна ( 1 , 2 ). Зворотний дзвінок виконується як мікрозадача, яка блокує подальшу обробку DOM і може запускатися сотні або тисячі разів на секунду на складній сторінці. Більшість прикладів та існуючих бібліотек не враховують подібні сценарії і пропонують гарний, простий у використанні, але потенційно повільний код JS.

  1. Завжди використовуйте профайлер devtools і намагайтеся, щоб ваш виклик спостерігача споживав менше 1% від загального часу процесора, який витрачається під час завантаження сторінки.

  2. Уникайте активації примусового синхронного макета , звертаючись до offsetTop та подібних властивостей

  3. Уникайте використання складних DOM-фреймворків / бібліотек, таких як jQuery, віддайте перевагу власним DOM-матеріалам

  4. Під час спостереження за атрибутами використовуйте attributeFilter: ['attr1', 'attr2']опцію в .observe().

  5. По можливості спостерігайте за прямими батьками нерекурсивно ( subtree: false).
    Наприклад, має сенс дочекатися батьківського елемента, спостерігаючи documentрекурсивно, від'єднати спостерігача від успіху, прикріпити новий нерекурсивний елемент до цього елемента-контейнера.

  6. Чекаючи лише одного елемента з idатрибутом, використовуйте шалено швидко, getElementById замість того, щоб перераховувати mutationsмасив (він може мати тисячі записів): example .

  7. Якщо бажаний елемент є відносно рідкісним на сторінці (наприклад, iframeабо object), використовуйте поточний HTMLCollection, повернутий getElementsByTagNameі, getElementsByClassNameі перевірте їх усі, замість того, щоб перераховувати, mutationsякщо він містить більше 100 елементів, наприклад.

  8. Уникайте використання querySelectorта особливо надзвичайно повільних querySelectorAll.

  9. Якщо querySelectorAllабсолютно неможливо уникнути зворотного виклику MutationObserver, спочатку виконайте querySelectorперевірку, а якщо вдало, продовжуйте querySelectorAll. У середньому така комбінація буде набагато швидшою.

  10. Якщо ви націлюєтеся на Chrome / ium до 2018 року, не використовуйте вбудовані методи Array, такі як forEach, фільтр тощо, які вимагають зворотних викликів, оскільки у V8 Chrome ці функції завжди були дорогими у порівнянні з класичним for (var i=0 ....)циклом (10-100 в рази повільніше), а зворотний виклик MutationObserver може повідомляти про тисячі вузлів на складних сучасних сторінках.

  • Альтернативне перерахування функцій, підкріплене lodash або подібною швидкою бібліотекою, добре навіть у старих браузерах.
  • Починаючи з 2018 року Chrome / ium включає стандартні вбудовані методи масиву.
  1. Якщо орієнтовані на браузери до 2019 року, не використовуйте повільні цикли ES2015, як for (let v of something)всередині зворотного виклику MutationObserver, якщо ви не зробите транпіляцію, щоб результуючий код працював так само швидко, як класичний forцикл.

  2. Якщо мета полягає в тому, щоб змінити вигляд сторінки, і у вас є надійний і швидкий спосіб повідомити, що додані елементи знаходяться за межами видимої частини сторінки, відключіть спостерігача та заплануйте цілу сторінку для повторної перевірки та обробки за допомогою setTimeout(fn, 0): це буде виконано, коли початковий сплеск розбору / компонування завершено, і двигун може "дихати", що може зайняти навіть секунду. Тоді ви можете непомітно обробити сторінку фрагментами, наприклад, за допомогою requestAnimationFrame.

  3. Якщо обробка є складною та / або займає багато часу, це може призвести до дуже довгих кадрів фарби, невідповідності / збитку, тому в цьому випадку ви можете використовувати дебанс або подібну техніку, наприклад, накопичувати мутації у зовнішньому масиві та планувати запуск через setTimeout / requestIdleCallback / requestAnimationFrame:

    const queue = [];
    const mo = new MutationObserver(mutations => {
      if (!queue.length) requestAnimationFrame(process);
      queue.push(mutations);
    });
    function process() {
      for (const mutations of queue) {
        // ..........
      }
      queue.length = 0;
    }
    

Повернутися до питання:

стежте за певним контейнером, ul#my-listщоб побачити, чи <li>до нього додано який-небудь .

Оскільки liце прямий дочірній продукт, і ми шукаємо додані вузли, єдиним необхідним варіантом є childList: true(див. Пораду №2 вище).

new MutationObserver(function(mutations, observer) {
    // Do something here

    // Stop observing if needed:
    observer.disconnect();
}).observe(document.querySelector('ul#my-list'), {childList: true});

2
Очевидно, я не можу відразу ж нагородити, але я даю цій відповіді 50-бальну нагороду, тому що я вважаю хитрість колекції DOM у прямому ефірі для перегляду розумних елементів! Хороша відповідь!
Бенджамін

Крім того, вам може знадобитися оновити список - наприклад, for... ofце так швидко, як звичайний цикл for, зараз у V8:]
Бенджамін

Так, нарешті це повільніше лише на 10% або навіть менше в останніх версіях Chrome.
wOxxOm

Був проміжний етап, коли він був перетворений у звичайний цикл (із лічильником), але, мабуть, він був замінений на власну ІЧ-інструкцію в github.com/v8/v8/commit/… - досить класна штука! У будь-якому випадку, for ... of vs for тут не має великого значення (варто згадати, що ця оптимізація призначена для масивів - тому поради все ж мають певні переваги для NodeLists)
Бенджамін

1
@Bergi, це для випадку, коли обробка є складною і займає багато часу. MutationObserver працює в черзі мікрозадач, тому декілька зворотних викликів все ще можуть міститися в одному завданні, що може призвести до дуже довгих кадрів фарби та збитку.
wOxxOm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.