javascript - uso - Rendimiento de MutationObserver para detectar nodos en DOM completo
mutationobserver jquery (1)
Estoy interesado en usar
MutationObserver
para detectar si un determinado elemento HTML se agrega en algún lugar de una página HTML.
Por ejemplo, diré que quiero detectar si se han agregado algunos
<li>
en cualquier parte del DOM.
Todos los ejemplos de
MutationObserver
que he visto hasta ahora solo detectan si se agrega un nodo a un contenedor en particular.
Por ejemplo:
algo de HTML
<body>
...
<ul id=''my-list''></ul>
...
</body>
MutationObserver
Definición del
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
});
Entonces, en este ejemplo, el
MutationObserver
está configurado para mirar un contenedor muy seguro (
ul#my-list
) para ver si se han agregado algunos
<li>
.
¿Es un problema si quisiera ser
menos específico
y estar atento a
<li>
en todo el cuerpo HTML de esta manera:
var container = document.querySelector(''body'');
Sé que funciona en los ejemplos básicos que configuré para mí ... ¿Pero no se recomienda hacer esto? ¿Esto va a resultar en un bajo rendimiento? Y si es así, ¿cómo detectaría y mediría ese problema de rendimiento?
Pensé que tal vez había una razón por la cual todos los ejemplos de
MutationObserver
son tan específicos con su contenedor objetivo ... pero no estoy seguro.
Esta respuesta se aplica a páginas grandes y complejas.
Especialmente si se adjunta un observador antes de que la página comience a cargarse (es decir,
document_start
/
document-start
en extensiones de Chrome / WebExtensions / userscripts o solo en una secuencia de comandos de página síncrona normal dentro de
<head>
), pero también en páginas enormes actualizadas dinámicamente, por ejemplo, comparación de rama en GitHub.
Una devolución de llamada no optimizada de MutationObserver puede agregar unos segundos al tiempo de carga de la página si la página es grande y compleja (
1
,
2
).
La mayoría de los ejemplos y las bibliotecas existentes no tienen en cuenta tales escenarios y ofrecen un código js atractivo, fácil de usar pero lento.
La devolución de llamada de MutationObserver se ejecuta como un microtask que bloquea el procesamiento posterior de DOM y se puede disparar cientos o miles de veces por segundo en una página compleja.
-
Utilice siempre el generador de perfiles de devtools e intente hacer que la devolución de llamada de su observador consuma menos del 1% del tiempo total de CPU consumido durante la carga de la página.
-
Evite activar el diseño síncrono forzado accediendo a offsetTop y propiedades similares
-
Evite usar marcos / bibliotecas DOM complejas como jQuery, prefiera cosas DOM nativas
-
Al observar los atributos, use la opción
attributeFilter: [''attr1'', ''attr2'']
en.observe()
. -
Siempre que sea posible, observe a los padres directos de manera no recursiva (
subtree: false
).
Por ejemplo, tiene sentido esperar al elemento padre observando eldocument
recursiva, desconectar al observador en caso de éxito, adjuntar uno nuevo no recursivo en este elemento contenedor. -
Cuando espere solo un elemento con un atributo
id
, usegetElementById
increíblemente rápido en lugar de enumerar la matriz demutations
(puede tener miles de entradas): example . -
En caso de que el elemento deseado sea relativamente raro en la página (por ejemplo,
iframe
uobject
), use la colección HTMLC en vivo devuelta porgetElementsByTagName
ygetElementsByClassName
y vuelva a verificarlos todos en lugar de enumerar lasmutations
si tiene más de 100 elementos, por ejemplo. -
Evite usar
querySelector
y especialmente elquerySelectorAll
extremadamente lento. -
Si
querySelectorAll
es absolutamente inevitable dentro de la devolución de llamada MutationObserver, primero realice una verificaciónquerySelector
, y si tiene éxito, continúe conquerySelectorAll
. En promedio, este combo será mucho más rápido. -
Si apunta a navegadores de borde sin sangrado, no use métodos de matriz integrados como forEach, filter, etc. que requieren devoluciones de llamada porque en Chrome V8 estas funciones siempre han sido costosas de invocar en comparación con el clásico
for (var i=0 ....)
bucle (10-100 veces más lento, pero el equipo V8 está trabajando en ello [2017]), y la devolución de llamada de MutationObserver puedeaddedNodes
100 veces por segundo con docenas, cientos o miles deaddedNodes
en cada lote de mutaciones en páginas modernas complejas .La alineación de los arrays incorporados no es universal, generalmente ocurre en código primitivo similar a un punto de referencia. En el mundo real, MutationObserver tiene picos de actividad intermitentes (como 1-1000 nodos reportados 100 veces por segundo) y las devoluciones de llamada nunca son tan simples como
return x * x
por lo que el código no se detecta como "caliente" lo suficiente como para estar en línea / optimizado .Sin embargo, la enumeración funcional alternativa respaldada por lodash o una biblioteca rápida similar está bien. A partir de 2018, Chrome y el V8 subyacente incorporarán los métodos integrados de la matriz estándar.
-
Si apunta a navegadores de borde sin sangrado, no use los bucles lentos de ES2015 como
for (let v of something)
dentro de la devolución de llamada MutationObserver a menos que transpile para que el código resultante se ejecute tan rápido como el bucle clásico. -
Si el objetivo es alterar el aspecto de la página y tiene un método confiable y rápido para decir que los elementos que se agregan están fuera de la parte visible de la página, desconecte el observador y programe una
setTimeout(fn, 0)
y reprocesamiento de la página completa a través desetTimeout(fn, 0)
: se ejecutará cuando finalice la explosión inicial de la actividad de análisis / diseño y el motor pueda "respirar", lo que podría tomar incluso un segundo. Luego, puede procesar discretamente la página en fragmentos utilizando requestAnimationFrame, por ejemplo.
De vuelta a la pregunta:
mire un contenedor muy seguro
ul#my-list
para ver si se le ha agregado alguna<li>
.
Como
li
es un hijo directo y buscamos nodos añadidos,
la única opción necesaria
es
childList: true
(consulte el consejo n.º 2 anterior).
new MutationObserver(function(mutations, observer) {
// Do something here
// Stop observing if needed:
observer.disconnect();
}).observe(document.querySelector(''ul#my-list''), {childList: true});