Fecha de publicación: 31 de diciembre de 2013
JavaScript nos permite modificar casi todos los aspectos de la página: contenido, el estilo y su respuesta a la interacción del usuario. Sin embargo, JavaScript también puede bloquear la construcción del DOM y demorar la presentación de la página. Para obtener un rendimiento óptimo, haz que tu JavaScript sea asíncrono y elimina todo JavaScript innecesario de la ruta de acceso de representación crítica.
Resumen
- JavaScript puede consultar y modificar el DOM y el CSSOM.
- La ejecución de JavaScript bloquea el CSSOM.
- JavaScript bloquea la construcción del DOM, a menos que se declare explícitamente como asíncrona.
JavaScript es un lenguaje dinámico que se ejecuta en un navegador y nos permite modificar prácticamente todos los aspectos del comportamiento de la página: podemos modificar contenido de la página agregando o eliminando elementos del árbol del DOM. También podemos modificar las propiedades del CSSOM de cada elemento y controlar las entradas del usuario, entre otras muchas opciones más. Para ilustrar esto, observa lo que sucede cuando la frase "Hello World" anterior ejemplo se modifica para agregar una secuencia de comandos intercalada corta:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
JavaScript nos permite acceder al DOM y extraer la referencia al nodo de intervalo oculto; Es posible que el nodo no esté visible en el árbol de representación, pero seguirá estando en el DOM. Luego, cuando tengamos la referencia, podremos cambiar su texto (mediante .textContent) e incluso anular su propiedad de estilo de visualización calculada pasando de "none" a "inline". Ahora nuestra página muestra “Hello interactive students!”.
JavaScript también nos permite crear nuevos elementos, aplicarles ajustes de estilo, agregarlos y quitarlos en el DOM. Técnicamente, toda nuestra página podría ser un solo archivo JavaScript grande que cree y diseñe los elementos uno por uno. Si bien esto funciona, en la práctica es mucho más fácil usar HTML y CSS. En la segunda parte de nuestra función de JavaScript, crearemos un nuevo elemento div, configuraremos su contenido de texto y lo adjuntaremos al cuerpo.
Con esto, modificamos el contenido y el estilo de CSS de un nodo del DOM existente y agregamos un nodo completamente nuevo al documento. Nuestra página no ganará ningún premio de diseño, pero ilustra la potencia y flexibilidad que JavaScript nos ofrece.
Sin embargo, si bien JavaScript nos brinda mucha capacidad, genera muchas limitaciones adicionales respecto de cómo y cuándo se representa la página.
Primero, observa que, en el ejemplo anterior, nuestra secuencia de comandos integrada se encuentra cerca de la parte inferior de la página. ¿Por qué? Deberías probarlo tú mismo, pero si movemos la secuencia de comandos por encima del elemento <span>
, verás que se producirá un error en esta y se te advertirá de que no puedes encontrar una referencia a ningún elemento <span>
en el documento; es decir, getElementsByTagName('span')
muestra null
. Esto demuestra una propiedad importante: nuestra secuencia de comandos se ejecuta en el punto exacto donde se inserta en el documento. Cuando el analizador de HTML encuentra una etiqueta de secuencia de comandos, pausa su proceso de construcción del DOM y le cede el control al motor JavaScript. una vez que el motor de JavaScript termina de ejecutarse, el navegador continúa donde lo dejó y reanuda la construcción del DOM.
En otras palabras, nuestro bloque de secuencia de comandos no podrá encontrar ningún elemento más adelante en la página porque todavía no se procesó. Dicho de otro modo: la ejecución de nuestra secuencia de comandos integrada bloquea la construcción del DOM, lo cual también demora la renderización inicial.
Otra propiedad sutil de la introducción de secuencias de comandos en nuestra página es que pueden leer y modificar no solo el DOM, sino también las propiedades de CSSOM. De hecho, eso es exactamente lo que estamos haciendo en nuestro ejemplo cuando cambiamos la propiedad de visualización del elemento span de none a inline. ¿El resultado final? Ahora tenemos una condición de carrera.
¿Qué ocurre si el navegador no ha terminado de descargar y compilar el CSSOM cuando queremos ejecutar nuestra secuencia de comandos? La respuesta no es muy buena para el rendimiento: el navegador demora la ejecución de la secuencia de comandos y la construcción del DOM hasta terminar de descargar y construir el CSSOM.
En resumen, JavaScript introduce muchas dependencias nuevas entre la ejecución del DOM, el CSSOM y JavaScript. Esto puede tener efectos adversos importantes en la velocidad con que el navegador procese y represente la página en la pantalla:
- La ubicación de la secuencia de comandos en el documento es importante.
- Cuando el navegador encuentra una etiqueta de secuencia de comandos, se pausa la construcción del DOM hasta que la secuencia de comandos termina de ejecutarse.
- JavaScript puede consultar y modificar el DOM y el CSSOM.
- La ejecución de JavaScript se detiene hasta que el CSSOM esté listo.
En gran medida, "optimización de la ruta de acceso de representación crítica" se refiere a la comprensión y optimización del gráfico de dependencias entre HTML, CSS y JavaScript.
Bloqueo del analizador en comparación con JavaScript asíncrono
De forma predeterminada, la ejecución de JavaScript "bloquea el analizador": cuando el navegador encuentra una secuencia de comandos en el documento, debe pausar la construcción del DOM, entregar el control al tiempo de ejecución de JavaScript y permitir que se ejecute la secuencia de comandos antes de continuar con la construcción del DOM. Ya vimos esto en acción en un ejemplo anterior con una secuencia de comandos integrada. De hecho, las secuencias de comandos intercaladas siempre bloquean el analizador, a menos que escribas código adicional para diferir su ejecución.
¿Qué ocurre con las secuencias de comandos que se incluyen con una etiqueta de secuencia de comandos? Observa el ejemplo anterior y extrae el código en un archivo separado:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script External</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
app.js
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
Si usamos un <script> o un fragmento de JavaScript intercalado, esperar que ambos se comporten de la misma manera. En ambos casos, el navegador pausa y ejecuta la secuencia de comandos para poder procesar el resto del documento. Sin embargo, en el caso de un archivo JavaScript externo, el navegador debe hacer una pausa para espera a que la secuencia de comandos se recupere del disco, la caché o un servidor remoto, que puede aumentar de decenas a miles de milisegundos de retraso a la renderización crítica ruta de acceso.
De forma predeterminada, JavaScript bloquea a los analizadores. Como el navegador no sabe lo que la secuencia de comandos planea hacer en la página, supone el peor de los casos y bloquea el analizador. Una señal enviada al navegador de que "la secuencia de comandos no necesita ejecutarse en el punto exacto donde se hace referencia" permite que el navegador continúe construyendo el DOM y permite que la secuencia de comandos se ejecute cuando esté lista. Por ejemplo, luego de que el archivo se obtiene de la caché o un servidor remoto.
Para lograrlo, se agrega el atributo async
al elemento <script>
:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script Async</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Agregar la palabra clave async a la etiqueta de secuencia de comandos indica al navegador que no bloquee la construcción del DOM mientras espera que la secuencia de comandos esté disponible, lo que puede mejorar el rendimiento de manera significativa.