¿Cuáles son algunos de los problemas estándar o patrones de codificación en jQuery que provocan pérdidas de memoria?
He visto una serie de preguntas relacionadas con la llamada ajax() o la eliminación de jsonp o DOM en StackOverflow. La mayoría de las preguntas sobre fugas de memoria de jQuery se centran en problemas o navegadores específicos y sería bueno tener una lista de los patrones estándar de fugas de memoria en jQuery.
Aquí hay algunas preguntas relacionadas con SO:
Recursos en la web:
Por lo que entiendo, la administración de memoria en javascript se logra mediante el conteo de referencias; mientras exista una referencia a un objeto, no se desasignará. Esto significa que crear una fuga de memoria en una aplicación de una sola página es trivial y puede hacer tropezar a aquellos que provienen 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 mire el uso de la memoria. Las instancias de MyObject nunca se desasignan mientras la página está activa, debido al puntero "_this" (aumente el valor máximo de i para verlo de manera más dramática). (En versiones anteriores de IE, nunca se desasignaban hasta que se cerraba el programa). Dado que los objetos javascript pueden compartirse entre marcos (no recomiendo probar esto porque es muy temperamental), hay casos en los que incluso en un navegador moderno javascript los objetos pueden quedarse mucho más tiempo de lo que deberían.
En el contexto de jquery, las referencias a menudo se almacenan para ahorrar la sobrecarga de la búsqueda de dom, por ejemplo:
function run(){ var domObjects = $(".myClass"); domObjects.click(function(){ domObjects.addClass(".myOtherClass"); }); }Este código retendrá 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 pasado por alto instancias como esta internamente, entonces la biblioteca misma se filtrará, pero más a menudo es el código del cliente.
El segundo ejemplo se puede corregir borrando explícitamente el puntero cuando ya no sea necesario:
function run(){ var domObjects = $(".myClass"); domObjects.click(function(){ if(domObjects){ domObjects.addClass(".myOtherClass"); domObjects = null; } }); }o haciendo la búsqueda de nuevo:
function run(){ $(".myClass").click(function(){ $(".myClass").addClass(".myOtherClass"); }); }Una buena regla general es tener cuidado al definir las funciones de devolución de llamada y evitar anidar demasiado cuando sea posible.
Editar: como se señaló en los comentarios de Erik, también podría usar este puntero para evitar la búsqueda innecesaria de dom:
function run(){ $(".myClass").click(function(){ $(this).addClass(".myOtherClass"); }); }Contribuiré con un antipatrón aquí, que es la fuga de "referencia a la mitad de la cadena".
Una de las fortalezas de jQuery es su API de encadenamiento, que le permite continuar cambiando, filtrando y manipulando los elementos:
$(".message").addClass("unread").find(".author").addClass("noob"); Al final de esa cadena, tiene un objeto jQuery con todos los elementos ".message .author", pero ese objeto se refiere a un objeto con los elementos originales ".message". Puede llegar a ellos a través del método .end() y hacerles algo:
$(".message") .find(".author") .addClass("prolific") .end() .addClass("unread");Ahora, cuando se usa de esta manera, no hay problemas con las fugas. Sin embargo, si asigna el resultado de una cadena a una variable que tiene una vida larga, las referencias anteriores a conjuntos anteriores se mantienen y no se pueden recolectar basura hasta que la variable quede fuera del alcance. Si esa variable es global, las referencias nunca quedan fuera del alcance.
Entonces, por ejemplo, supongamos que leyó en una publicación de blog de 2008 que $("a").find("b") era "más eficiente" que $("ab") (aunque no vale la pena siquiera pensar en tal una micro-optimización). Decide que necesita una página global para contener una lista de autores, por lo que hace esto:
authors = $(".message").find(".author");Ahora tiene un objeto jQuery con la lista de autores, pero también se refiere a un objeto jQuery que es la lista completa de mensajes. Probablemente nunca lo use o ni siquiera sepa que está allí, y está ocupando memoria.
Tenga en cuenta que las fugas solo pueden ocurrir con los métodos que seleccionan nuevos elementos de un conjunto existente, como .find , .filter , .children , etc. Los documentos indican cuándo se devuelve un nuevo conjunto. El simple uso de una API de encadenamiento no causa una fuga si la cadena tiene métodos simples sin filtrado como .css , por lo que está bien:
authors = $(".message .author").addClass("prolific");