43

¿Cuáles son algunos de los problemas estándar o patrones de codificación en jQuery que conducen a pérdidas de memoria?jQuery patrones de fuga de memoria y causas


he visto una serie de preguntas relacionadas con la llamada AJAX() o jsonp o la eliminación de DOM en StackOverflow. La mayoría de las preguntas sobre la fuga de memoria de jQuery están enfocadas en problemas específicos o navegadores y sería bueno tener una lista de los patrones de fuga de memoria estándar en jQuery.

Estas son algunas de las preguntas relacionadas con el SO:

Recursos en la web:

Respuesta

28

Por lo que entiendo, la gestión de memoria en JavaScript se logra mediante el recuento de referencias - mientras que una referencia a un objeto todavía existe, no se cancela la asignación. Esto significa que la creación de una pérdida de memoria en una aplicación de una sola página es trivial, y puede tropezar con las de uso provenientes de un fondo de Java. Esto no es específico de JQuery. Tome el siguiente código, por ejemplo:

function MyObject = function(){ 
    var _this = this; 
    this.count = 0; 
    this.getAndIncrement = function(){ 
     _this.count++; 
     return _this.count; 
    } 
} 

for(var i = 0; i < 10000; i++){ 
    var obj = new MyObject(); 
    obj.getAndIncrement(); 
} 

Se verá normal hasta que vea el uso de la memoria. Las instancias de MyObject nunca se desasignan mientras la página está activa, debido al puntero "_this" (aumenta el valor máximo de i para verlo más dramáticamente). (En versiones anteriores de IE, nunca fueron desasignados hasta que el programa finaliza). Dado que los objetos javascript pueden compartirse entre fotogramas (no recomiendo probar esto, ya que es muy temperamental), hay casos en los que incluso en un navegador moderno JavaScript los objetos pueden permanecer mucho más tiempo de lo que deberían.

En el contexto de jQuery, las referencias A menudo se utilizan para guardar la sobrecarga de búsqueda dom - por ejemplo:

function run(){ 
    var domObjects = $(".myClass"); 
    domObjects.click(function(){ 
     domObjects.addClass(".myOtherClass"); 
    }); 
} 

Este código se aferrará a DOMObject (y todo su contenido) para siempre, debido a la referencia a él en la función de devolución de llamada.

Si los escritores de jquery han perdido instancias como esta internamente, entonces la biblioteca en sí misma tendrá fugas, pero más a menudo es el código del cliente.

El segundo ejemplo se puede fijar en la limpieza de forma explícita el puntero cuando ya no se requiere:

function run(){ 
    var domObjects = $(".myClass"); 
    domObjects.click(function(){ 
     if(domObjects){ 
      domObjects.addClass(".myOtherClass"); 
      domObjects = null; 
     } 
    }); 
} 

o hacer la búsqueda de nuevo:

function run(){ 
    $(".myClass").click(function(){ 
     $(".myClass").addClass(".myOtherClass"); 
    }); 
} 

Una buena regla general es ser Tenga cuidado cuando defina sus funciones de devolución de llamada, y evite anidar demasiado cuando sea posible.

Edit: Como se ha señalado en los comentarios de Erik, también se puede usar el puntero this para evitar la unnescessary de búsqueda dom:

function run(){ 
    $(".myClass").click(function(){ 
     $(this).addClass(".myOtherClass"); 
    }); 
} 
+2

He aquí un ejemplo: http://jsfiddle.net/qTu6y/8/ Puede usar "Hacer una foto" de Chrome en el generador de perfiles para ver que cada ejecución de ese bloque de código consume alrededor de 20 MB de RAM. (Mientras lo probaba, pulsé "Script usó demasiado RAM error en Chrome") – Raynos

+0

+1 para "aquellos de nosotros que venimos de un fondo Java" ... y por la explicación lúcida. – Thimmayya

+1

$ (esto) .addClass sería mejor para el rendimiento en el último caso ya que 'esto' representa una colección de elementos DOM JS central (que es lo que los objetos JQuery suelen envolver con un patrón de estilo adaptador) y JQ no tendrá que acceda nuevamente al DOM o analice cada elemento DOM en la página en el caso de

15

voy contribuirán con un anti-patrón aquí, que es el fuga de "referencia a mitad de cadena".

Uno de los puntos fuertes de jQuery es su API de encadenamiento, lo que le permite continuar a cambiar, filtrar y manipular los elementos:

$(".message").addClass("unread").find(".author").addClass("noob"); 

Al final de esa cadena tiene un objeto jQuery con todos los". mensaje .author "elementos, pero ese objeto hace referencia a objetos con los elementos originales" .message "y los objeta. Puede llegar a ellos a través del método .end() y hacer algo con ellos:

$(".message") 
    .find(".author") 
    .addClass("prolific") 
    .end() 
    .addClass("unread"); 

Ahora cuando se usa de esta manera no hay problemas con fugas. Sin embargo, si asigna el resultado de una cadena a una variable que tiene una larga vida útil, las referencias anteriores a conjuntos anteriores permanecen y no se pueden recolectar basura hasta que la variable salga del alcance. Si esa variable es global, las referencias nunca salen del alcance.

Por ejemplo, digamos que leyó en una publicación de blog de 2008 que $("a").find("b") fue "más eficiente" que $("a b") (aunque no es digno de siquiera pensar en tal micro-optimización). Decide que necesita una página de ancho mundial para mantener una lista de autores por lo que hace esto:

authors = $(".message").find(".author"); 

Ahora usted tiene un objeto jQuery con la lista de autores, pero también se refiere de nuevo a un objeto jQuery que es la lista completa de mensajes. Probablemente nunca lo usarás o incluso sabrás que está ahí, y está ocupando la memoria.

Nota que las fugas sólo pueden ocurrir con los métodos que seleccionan nuevos elementos de un conjunto existente, tal como .find, .filter, .children etc. Los documentos indican cuando se devuelve un conjunto nuevo. El simple uso de una API de encadenamiento no causa una fuga si la cadena tiene métodos no filtrado simples como .css, así que esto está bien:

authors = $(".message .author").addClass("prolific");