Apéndice C: JavaScript en HTML
La relación entre HTML, CSS y JavaScript
HTML define la estructura y el contenido. CSS define la presentación. JavaScript define el comportamiento: responde a las acciones del usuario, modifica el contenido en tiempo real, comunica con servidores y orquesta la lógica de la interfaz.
Los tres son independientes y complementarios. Una página puede existir sin CSS y sin JavaScript, pero no sin HTML. CSS puede funcionar sin JavaScript. JavaScript, en cambio, necesita un documento HTML para tener algo sobre lo que actuar.
Este apéndice cubre solo el punto de contacto entre JavaScript y HTML: cómo se incluye, cómo accede al documento y cómo respeta la separación de responsabilidades. Para el lenguaje en sí, existe un manual completo.
El elemento <script>
JavaScript se incluye en HTML de dos maneras. La primera, con el código directamente en el atributo src apuntando a un archivo externo (la forma recomendada):
<script src="/js/app.js"></script>
La segunda, con el código inline entre las etiquetas (aceptable para fragmentos pequeños o configuración inicial):
<script>
console.log('Hola desde el HTML');
</script>
El archivo externo tiene las mismas ventajas que una hoja de estilos externa: se cachea, se reutiliza en varias páginas y mantiene el HTML limpio.
Dónde colocar el script
El navegador procesa el HTML de arriba a abajo. Cuando encuentra un <script>, detiene el análisis del documento, descarga y ejecuta el script, y solo entonces continúa. Si el script está en el <head> y el código intenta acceder a elementos del <body>, esos elementos todavía no existen.
La solución clásica era colocar todos los scripts al final del <body>, justo antes del cierre </body>:
<body>
<!-- todo el contenido -->
<script src="/js/app.js"></script>
</body>
Así el HTML ya estaba completo cuando el script se ejecutaba. Hoy existe una solución mejor: los atributos defer y async.
defer y async
Ambos atributos solo funcionan con scripts externos (con src). Cambian cuándo se descarga y cuándo se ejecuta el script:
<!-- defer: se descarga en paralelo, se ejecuta cuando el HTML está completo -->
<script src="/js/app.js" defer></script>
<!-- async: se descarga en paralelo, se ejecuta en cuanto termina la descarga -->
<script src="/js/analytics.js" async></script>
defer es lo recomendado para la mayoría de scripts: la descarga no bloquea el renderizado y la ejecución espera a que el documento esté listo, respetando además el orden si hay varios scripts. async no garantiza el orden de ejecución y es adecuado para scripts completamente independientes del documento y entre sí, como los de analítica.
Con defer, el script puede vivir en el <head> sin problemas:
<head>
<meta charset="UTF-8">
<title>Mi página</title>
<link rel="stylesheet" href="/css/estilos.css">
<script src="/js/app.js" defer></script>
</head>
JavaScript y el DOM
Cuando el navegador carga una página HTML, construye una representación interna del documento en memoria: el DOM (Document Object Model). El DOM es un árbol de nodos que refleja la estructura del HTML: cada etiqueta es un nodo, cada atributo es una propiedad, cada texto es un nodo de texto.
La idea clave es que al construir ese árbol, cada elemento deja de ser texto en un archivo para convertirse en un objeto con identidad propia: tiene propiedades, métodos y una posición única en el árbol. Eso es lo que hace posible referirse a un párrafo concreto, a una imagen concreta o a un botón concreto -y solo a ese- para leerlo, modificarlo o eliminarlo sin tocar nada más.
JavaScript accede y modifica ese árbol a través del objeto document:
<p id="saludo">Hola</p>
<script>
// Acceder a un elemento por su id
const parrafo = document.getElementById('saludo');
// Leer su contenido
console.log(parrafo.textContent); // "Hola"
// Modificar su contenido
parrafo.textContent = 'Hola, mundo';
// Cambiar un estilo
parrafo.style.color = 'green';
// Añadir una clase CSS
parrafo.classList.add('destacado');
</script>
Lo que JavaScript modifica es el DOM en memoria, no el archivo HTML original. Si se recarga la página, el HTML vuelve a su estado inicial.
El modelo de eventos
La mayor parte de lo que hace JavaScript en el navegador ocurre en respuesta a eventos: el usuario hace clic, mueve el ratón, escribe en un campo, envía un formulario, la página termina de cargarse.
<button id="mi-boton">Haz clic</button>
<script>
const boton = document.getElementById('mi-boton');
boton.addEventListener('click', function() {
alert('¡Clic!');
});
</script>
addEventListener registra una función que se ejecutará cuando ocurra el evento. El primer argumento es el nombre del evento ('click', 'submit', 'input', 'keydown'...), el segundo es la función a ejecutar.
El patrón es siempre el mismo: seleccionar un elemento, escuchar un evento, ejecutar una función. Todo lo demás es variación sobre esa base.
La separación de responsabilidades
Del mismo modo que CSS no debería estar mezclado con el HTML (atributos style inline en cada elemento), JavaScript tampoco. Los atributos de evento inline como onclick, onmouseover o onsubmit directamente en el HTML son el equivalente al CSS inline: funcionan, pero mezclan comportamiento con estructura y dificultan el mantenimiento.
<!-- Evitar: comportamiento mezclado con estructura -->
<button onclick="alert('clic')">Botón</button>
<!-- Preferible: comportamiento separado en el script -->
<button id="btn-accion">Botón</button>
<script>
document.getElementById('btn-accion').addEventListener('click', () => {
alert('clic');
});
</script>
La separación tiene un beneficio práctico además del estético: un script externo puede gestionar el comportamiento de toda la página desde un solo lugar, sin necesidad de abrir el HTML cada vez que cambia la lógica.
Para profundizar en el lenguaje, los tipos de datos, las estructuras de control, las funciones y la programación asíncrona, el manual de JavaScript está disponible en esta misma plataforma.
- Anterior « Specimen de elementos HTML
- Siguiente Fin del tema »