tag selectores examples ejemplos child avanzados javascript jquery ajax memory-leaks

javascript - selectores - Fuga de memoria que implica solicitudes jQuery Ajax



selectores jquery avanzados (3)

No estoy seguro acerca de la fuga, pero su función resetTable() es muy ineficiente. Intente solucionar esos problemas primero y vea dónde termina.

  • No anexar al DOM en un bucle. Si debe hacer la manipulación de DOM, a continuación, agregue un fragmento de documento y luego mueva ese fragmento al DOM.
  • Pero innerHTML es más rápido que la manipulación DOM de todos modos, así que úsala si puedes.
  • Almacene los conjuntos de jQuery en variables locales, sin necesidad de volver a ejecutar el selector cada vez.
  • También almacene referencias repetidas en una variable local.
  • Al iterar sobre una colección de cualquier tipo, también almacene la longitud en una variable local.

Nuevo código:

<html> <head> <title>Test Page</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> </head> <body> <script type="text/javascript"> $(function() { var $tbody = $("#content tbody"); function kickoff() { $.getJSON("test.php", resetTable); } function resetTable(rows) { var html = '''' , i = 0 , l = rows.length , row; for ( ; i < l; i++ ) { row = rows[i]; html += "<tr>" + "<td>" + row.mpe_name + "</td>" + "<td>" + row.bin + "</td>" + "<td>" + row.request_time + "</td>" + "<td>" + row.filtered_delta + "</td>" + "<td>" + row.failed_delta + "</td>" + "</tr>"; } $tbody.html( html ); setTimeout(kickoff, 2000); } kickoff(); }); </script> <table id="content" border="1" style="width:100% ; text-align:center"> <thead> <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th> </thead> <tbody></tbody> </table> </body> </html>

Referencias

Tengo una página web que está perdiendo memoria en IE8 y Firefox; el uso de la memoria que se muestra en Windows Process Explorer simplemente sigue creciendo con el tiempo.

La siguiente página solicita la url "unplanned.json", que es un archivo estático que nunca cambia (aunque configuro mi encabezado HTTP Cache-control en no-cache para asegurarme de que la solicitud Ajax siempre se realiza). Cuando obtiene los resultados, borra una tabla HTML, realiza un bucle sobre la matriz json que recuperó del servidor y agrega dinámicamente una fila a una tabla HTML para cada entrada en la matriz. Luego espera 2 segundos y repite este proceso.

Aquí está toda la página web:

<html> <head> <title>Test Page</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> </head> <body> <script type="text/javascript"> function kickoff() { $.getJSON("unplanned.json", resetTable); } function resetTable(rows) { $("#content tbody").empty(); for(var i=0; i<rows.length; i++) { $("<tr>" + "<td>" + rows[i].mpe_name + "</td>" + "<td>" + rows[i].bin + "</td>" + "<td>" + rows[i].request_time + "</td>" + "<td>" + rows[i].filtered_delta + "</td>" + "<td>" + rows[i].failed_delta + "</td>" + "</tr>").appendTo("#content tbody"); } setTimeout(kickoff, 2000); } $(kickoff); </script> <table id="content" border="1" style="width:100% ; text-align:center"> <thead><tr> <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th> </tr></thead> <tbody></tbody> </table> </body> </html>

Si ayuda, aquí hay un ejemplo de la json que estoy enviando (es esta matriz exacta con miles de entradas en lugar de solo una):

[ { mpe_name: "DBOSS-995", request_time: "09/18/2009 11:51:06", bin: 4, filtered_delta: 1, failed_delta: 1 } ]

EDITAR: He aceptado la respuesta extremadamente útil de Toran, pero creo que debería publicar un código adicional, ya que su plugin jQuery de removefromdom tiene algunas limitaciones:

  • Solo elimina elementos individuales. Por lo tanto, no puede darle una consulta como `$ (" # content tbody tr ")` y esperar que elimine todos los elementos que ha especificado.
  • Cualquier elemento que elimine debe tener un atributo `id`. Entonces, si quiero eliminar mi `tbody`, entonces debo asignar un` id` a mi etiqueta `tbody` o de lo contrario dará un error.
  • Elimina el elemento en sí y todos sus descendientes, por lo que si simplemente desea vaciar ese elemento, deberá volver a crearlo después (o modificar el complemento para vaciarlo en lugar de eliminarlo).

Así que aquí está mi página anterior modificada para usar el complemento de Toran. En aras de la simplicidad, no apliqué ninguno de los consejos generales de rendimiento ofrecidos por Peter . Aquí está la página que ahora ya no tiene pérdidas de memoria:

<html> <head> <title>Test Page</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> </head> <body> <script type="text/javascript"> <!-- $.fn.removefromdom = function(s) { if (!this) return; var el = document.getElementById(this.attr("id")); if (!el) return; var bin = document.getElementById("IELeakGarbageBin"); //before deleting el, recursively delete all of its children. while (el.childNodes.length > 0) { if (!bin) { bin = document.createElement("DIV"); bin.id = "IELeakGarbageBin"; document.body.appendChild(bin); } bin.appendChild(el.childNodes[el.childNodes.length - 1]); bin.innerHTML = ""; } el.parentNode.removeChild(el); if (!bin) { bin = document.createElement("DIV"); bin.id = "IELeakGarbageBin"; document.body.appendChild(bin); } bin.appendChild(el); bin.innerHTML = ""; }; var resets = 0; function kickoff() { $.getJSON("unplanned.json", resetTable); } function resetTable(rows) { $("#content tbody").removefromdom(); $("#content").append(''<tbody id="id_field_required"></tbody>''); for(var i=0; i<rows.length; i++) { $("#content tbody").append("<tr><td>" + rows[i].mpe_name + "</td>" + "<td>" + rows[i].bin + "</td>" + "<td>" + rows[i].request_time + "</td>" + "<td>" + rows[i].filtered_delta + "</td>" + "<td>" + rows[i].failed_delta + "</td></tr>"); } resets++; $("#message").html("Content set this many times: " + resets); setTimeout(kickoff, 2000); } $(kickoff); // --> </script> <div id="message" style="color:red"></div> <table id="content" border="1" style="width:100% ; text-align:center"> <thead><tr> <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th> </tr></thead> <tbody id="id_field_required"></tbody> </table> </body> </html>

NUEVAMENTE EDITAR: Dejaré mi pregunta sin cambios, aunque vale la pena señalar que esta pérdida de memoria no tiene nada que ver con Ajax. De hecho, el siguiente código tendría la misma fuga de memoria y se solucionaría tan fácilmente con el plugin jQuery removefromdom de Toran:

function resetTable() { $("#content tbody").empty(); for(var i=0; i<1000; i++) { $("#content tbody").append("<tr><td>" + "DBOSS-095" + "</td>" + "<td>" + 4 + "</td>" + "<td>" + "09/18/2009 11:51:06" + "</td>" + "<td>" + 1 + "</td>" + "<td>" + 1 + "</td></tr>"); } setTimeout(resetTable, 2000); } $(resetTable);


Por favor, corrígeme si me equivoco aquí pero no establece SetTimeout (fn) impide la liberación del espacio de memoria de la persona que llama? ¿Para que todas las variables / memoria asignadas durante el método resetTable (filas) permanezcan asignadas hasta que termine el ciclo?

Si ese es el caso, empujar la construcción de cuerdas y la lógica de appendTo a un método diferente puede ser un poco mejor ya que esos objetos se liberarían después de cada llamada y solo la memoria del valor devuelto (en este caso, el marcado de cadena o nada si nuevo método el appendTo ()) permanecería en la memoria.

En esencia:

Llamada inicial al inicio

-> Llamadas resetTable ()

-> -> Inicio de las llamadas a SetTimeout de nuevo

-> -> -> Llamadas resetTable () otra vez

-> -> -> -> Continuar hasta infinito

Si el código nunca se resuelve realmente, el árbol continuará creciendo.

Una forma alternativa de liberar algo de memoria basada en esto sería como el siguiente código:

function resetTable(rows) { appendRows(rows); setTimeout(kickoff, 2000); } function appendRows(rows) { var rowMarkup = ''''; var length = rows.length var row; for (i = 0; i < length; i++) { row = rows[i]; rowMarkup += "<tr>" + "<td>" + row.mpe_name + "</td>" + "<td>" + row.bin + "</td>" + "<td>" + row.request_time + "</td>" + "<td>" + row.filtered_delta + "</td>" + "<td>" + row.failed_delta + "</td>" + "</tr>"; } $("#content tbody").html(rowMarkup); }

Esto agregará el marcado a tu tbody y luego terminará esa parte de la pila. Estoy bastante seguro de que cada iteración de "filas" permanecerá en la memoria; sin embargo, la cadena de marcado y tal debería liberarse eventualmente.

De nuevo ... ha pasado un tiempo desde que miré SetTimeout en este nivel bajo, así que podría estar completamente equivocado aquí. En cualquier caso, esto no eliminará la fuga y solo posiblemente disminuirá la tasa de crecimiento. Depende de cómo el recolector de basura de los motores de JavaScript en uso se ocupa de los bucles SetTimeout como los que tiene aquí.


No estoy seguro de por qué Firefox no está contento con esto, pero puedo decir por experiencia que en IE6 / 7/8 debe establecer innerHTML = ""; en el objeto que quieres eliminar del DOM. (si usted creó este elemento DOM dinámicamente)

$("#content tbody").empty(); podría no estar liberando estos elementos DOM generados dinámicamente.

En su lugar, intente algo como el siguiente (este es un plugin jQuery que escribí para resolver el problema).

jQuery.fn.removefromdom = function(s) { if (!this) return; var bin = $("#IELeakGarbageBin"); if (!bin.get(0)) { bin = $("<div id=''IELeakGarbageBin''></div>"); $("body").append(bin); } $(this).children().each( function() { bin.append(this); document.getElementById("IELeakGarbageBin").innerHTML = ""; } ); this.remove(); bin.append(this); document.getElementById("IELeakGarbageBin").innerHTML = ""; };

Usted llamaría esto así: $("#content").removefromdom();

El único problema aquí es que necesita volver a crear su tabla cada vez que quiera construirla.

Además, si esto resuelve tu problema en IE, puedes leer más sobre esto en una publicación de blog que escribí a principios de este año cuando me encontré con el mismo problema.

Editar : actualicé el complemento anterior para que esté libre de JavaScript en un 95% ahora, así que está usando más jQuery que la versión anterior. Todavía notarás que tengo que usar innerHTML porque la función jQuery html (""); no funciona igual para IE6 / 7/8