Contenido

Analizando las formas de optimizar el trabajo con DOM en Javascript

20 sep

+ 6

Bjorn Tipling, publicó ayer un artículo con algunos consejos con los que mejorar el rendimiento de las aplicaciones javascript que interactuan con DOM, pero he estado haciendo pruebas de rendimiento y creo que es necesario comentarlas aqui para que veamos que es más optimo y que no lo és.

Escenario

El escenario en donde he realizado las pruebas es el siguiente:

1. No usar operaciones DOM para crear nodos.

Bjorn, comenta que la mejor opción para la creación de nodos es usar parentNode.innerHTML, pero … los resultados son diferentes. 

Código

var rep = 10000; //10.000
var parentNode = $('res');

var func = function() {
parentNode.innerHTML = "<div>Stuff</div>"
};

var func2 = function() {
var d = document.createElement("div");
d.nodeValue = "Stuff";
parentNode.childNodes[0] = d;
};

timer.start();
for (var x=0; x<rep; x++) func();
console.log("[NO_DOM]: " + timer.since());

timer.start();
for (var x=0; x<rep; x++) func2();
console.log("[DOM]: " + timer.since());

Resultados

[NO_DOM]: 2491
[DOM]: 705

 2. Como excepción a la anterior, en los casos de insercción a continuación de lo que ya había, si que se recomienda usar DOM.

El comando parentNode.innerHTML +=, consume una cantidad insana de tiempo, esto es una realidad y la prueba lo ha demostrado. He reducido el numero de repeticiones a 1.000 para evitar que se colgara el navegador. 

Código

var rep = 1000; //1.000
var parentNode = $('res');

var func = function() {
parentNode.innerHTML += "<div>Stuff</div>"
};

var func2 = function() {
node = document.createElement("div");
node.innerHTML = "<div>More Stuff</div>";
parentNode.appendChild(node.childNodes[0]);
};

timer.start();
for (var x=0; x<rep; x++) func();
console.log("[NO_DOM]: " + timer.since());

timer.start();
for (var x=0; x<rep; x++) func2();
console.log("[DOM]: " + timer.since());

Resultados

[NO_DOM]: 40210
[DOM]: 344

3. No concatenes cadenas con +=

Bjorn publica unos ejemplos de como concatenar cadenas, pero las pruebas indican que los resultados no son muy diferentes.

Código

var rep = 10000; //10.000
var parentNode = $('res');

var func = function() {
html = "";
html += "<div>"
html += "Content"
html += "</div>"
parentNode.innerHTML = html;
};

var func2 = function() {
html2 = [];
html2.push("<div>")
html2.push("Content")
html2.push("</div>")
parentNode.innerHTML = html2.join("");
};

timer.start();
for (var x=0; x<rep; x++) func();
console.log("[NO_DOM]: " + timer.since());

timer.start();
for (var x=0; x<rep; x++) func2();
console.log("[DOM]: " + timer.since());

Resultados

[NO_DOM]: 2804
[DOM]: 2850

4. Dale un respiro a tu navegador despues de una buena tanda de insercciones DOM.

Parece obvio pensar que deberíamos darle un descanso a nuestro navegador despues de un intenso trabajo con DOM.

Código

var rep = 1000; //10.000
var parentNode = $('res');

function animation() {
    console.log("Descanso");
};
var func = function() {
parentNode.innerHTML = "<div>Stuff</div>"
setTimeout(function(){animation();},0);
};

var func2 = function() {
var d = document.createElement("div");
d.nodeValue = "Stuff";
parentNode.childNodes[0] = d;
setTimeout(function(){animation();},0);
};

timer.start();
for (var x=0; x<rep; x++) func();
console.log("[NO_DOM]: " + timer.since());

timer.start();
for (var x=0; x<rep; x++) func2();
console.log("[DOM]: " + timer.since());

Resultados

[NO_DOM]: 485
[DOM]: 204

5. Ten cuidado con el tamaño de tu javascript.

El tamaño del nuestro javascript, no solo debe ser pequeño para evitar que la carga del usuario sea larga o pesada. Tambien hemos de pensar que el fichero es procesado por el navegador y a mayor tamaño, mayor procesamiento requiere.

6. Cargar javascript cuando lo necesitemos

Una solución para evitar que todo el javascript sea cargado al inicio, es la de segmentarlo e ir cargandolo cuando necesitemos.

7. Comprime tu javascript, 8. Gzip te ayuda

Evidentemente si comprimimos el JS, el tamaño del mismo será menor y por lo tanto ahorraremos tiempo de carga.

Código

var rep = 1000; //10.000
var parentNode = $('res');

var func = function() {
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
};

var func2 = function() {
eval(function(p,a,c,k,e,d){e=function(c){return c};if(!''.replace(/^/,String)){while(c--){d[c]=k[c]||c}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";2.1="0";',3,3,'HOLA|innerHTML|parentNode'.split('|'),0,{}))
};

timer.start();
for (var x=0; x<rep; x++) func();
console.log("[NO_DOM]: " + timer.since());

timer.start();
for (var x=0; x<rep; x++) func2();
console.log("[DOM]: " + timer.since());

Resultado

[NORMAL]: 4731
[COMPRESS]: 7347

En este ejemplo pretendo mostrar la diferencia de tiempo que tarda el PC en responder a la descompresión de código, en func2(), tenemos la versión comprimida con Javascript Compressor (Packer de Dean Edwars) de func(). Para complementar esta prueba, he realizado la prueba con la compresión plana, osea eliminando los carácteres especiales y en blanco, dejando una línea de texto.

Código

var rep = 1000; //10.000
var parentNode = $('res');

var func = function() {
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
parentNode.innerHTML = "HOLA";
};

var func2 = function() {
parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";parentNode.innerHTML="HOLA";
};

timer.start();
for (var x=0; x<rep; x++) func();
console.log("[NO_DOM]: " + timer.since());

timer.start();
for (var x=0; x<rep; x++) func2();
console.log("[DOM]: " + timer.since());

Resultados

[NORMAL]: 4782
[COMPRESS]: 4594
  • Yo la verdad no he hecho las pruebas pero hace poco leí que en el Explorer la diferencia a favor del innerHtml era bastante pronunciada y proponían una función que según el navegador usar DOM o no.

    Curiosamente buscando el articulo en cuestión he encontrado este otro benchmark que insiste en que innerHTML es más rápido (http://www.gloo.ru/blogs/gloom.dhtml_javascript_benchmark._l_en.wiki.aspx).

  • Hola Andrés,

    Gracias por compartir los resultados de esta controversia. Se ve interesante, hasta lo que tenia entendido innerHTML es más rápido que usar DOM en IE6 pero puedo equivocarme.

    Lo que si me llamó la atención es tu ultima prueba. Creo que Bjorn se refería a comprimir el fichero usando jsmin o yuimin, los cuales solo comprimen el archivo de forma simple y plana, removiendo whitespace y comentarios.

    Packer es un método avanzado de compresión, por lo que genera un tiempo extra de carga al navegador para procesarlo, por ende suele ser un poco más lento de lo normal.

    Pero si usas jsmin “solo para achicar el código mas no comprimirlo”, en teoría será igual de rápido en ejecutar pero más rápido de bajar.

    Y por último se recomiendar usar yuimin + gzip por los motivos explicados arriba. y se recomienda NO usar packer+gzip, ya que la compresión (de gzip) no es tan efectiva por el complejo código de packer.

    Quizás aquí encuentres una mejor explicación.
    http://www.julienlecomte.net/blog/2007/08/21/gzip-your-minified-javascript-files/

    Un saludo!

  • Muy buenas pruebas, yo también he leído que innerHTML en IE es mucho mas rápido, obvio si este es nativo en IE.

    Lo que me extraño fue tu ultimo ejemplo que no lo explicas, me da la impresión que quieres decir que comprimir con “packer” es mas lento, y es obvio porque agregar un proceso mas que es descomprimir y después evaluar el código.

    Saludos

  • Neojp, Victor, teneis razón tendría que haberlo explicado mejor, ahora me pongo a ello.

    Mi idea era la de poner un ejemplo de rendimiento entre el código comprimido y el sin comprimir.

    Esta claro que el proceso de descompresión consumirá más recursos, pero en algunos casos debemos sopesar su uso, frente al de carga de página.

    Es únicamente un ejemplo.

    Saludos.

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.