El BOM: window, location, history y navigator en JavaScript
En esta lección
Con el DOM controlas el contenido de la página. Pero hay cosas que no están en la página, sino en el navegador que la muestra: el tamaño de la ventana, la dirección de la barra, el botón “Atrás”, el idioma del usuario o si tiene conexión. Todo eso se maneja con el BOM (Browser Object Model, modelo de objetos del navegador). En esta lección verás sus objetos principales: window, location, history, navigator y screen, además de las clases URL y URLSearchParams para leer y construir direcciones.
Qué es el BOM
El BOM es el conjunto de objetos que el navegador ofrece a JavaScript para hablar con él. A diferencia del DOM, durante años no tuvo un estándar oficial y cada navegador hacía lo suyo; hoy está recogido en la especificación de HTML y funciona igual en todos los navegadores modernos.
window
├── document → el DOM (la página)
├── location → la URL actual
├── history → el historial de la pestaña
├── navigator → datos del navegador
├── screen → datos de la pantalla
├── localStorage, setTimeout, alert, fetch...
Todo cuelga de window, que representa la pestaña del navegador. Como es el objeto global, puedes omitirlo: window.location y location son lo mismo, igual que window.alert() y alert().
Nota: el BOM solo existe en el navegador. En Node.js no hay
windownilocation; el objeto global allí esglobalThis(que en el navegador también apunta awindow).
window: la ventana
Tamaño de la ventana
| Propiedad | Qué mide |
|---|---|
window.innerWidth / innerHeight | Área visible de la página, en píxeles (con la barra de scroll) |
window.outerWidth / outerHeight | La ventana completa, con barras y bordes del navegador |
document.documentElement.clientWidth | Ancho visible sin la barra de scroll |
Cuando el usuario cambia el tamaño de la ventana (o gira el móvil) se dispara el evento resize:
window.addEventListener("resize", () => {
console.log(`Ahora mide ${window.innerWidth} px de ancho`);
});
resize se dispara muchísimas veces mientras se arrastra el borde. Si tu código hace algo pesado, aplica un debounce como en la lección de temporizadores.
Y si lo que quieres es saber si estás en “versión móvil” o “versión escritorio”, no compares números a mano: usa matchMedia, que entiende las mismas media queries que el CSS responsive y avisa solo cuando se cruza el límite:
const movil = window.matchMedia("(max-width: 600px)");
function aplicar() {
console.log(movil.matches ? "Diseño móvil" : "Diseño escritorio");
}
movil.addEventListener("change", aplicar);
aplicar();
Scroll
window.scrollY; // píxeles desplazados hacia abajo
window.scrollTo({ top: 0, behavior: "smooth" }); // volver arriba suavemente
window.scrollBy({ top: 400, behavior: "smooth" }); // bajar 400 px desde donde estás
document.querySelector("#contacto").scrollIntoView({ behavior: "smooth" }); // ir a un elemento
Un botón “volver arriba” que solo aparece al bajar:
const subir = document.querySelector("#subir");
window.addEventListener("scroll", () => {
subir.hidden = window.scrollY < 300;
});
subir.addEventListener("click", () => window.scrollTo({ top: 0, behavior: "smooth" }));
Para saber si un elemento se ve en pantalla (cargar imágenes al llegar, animar al aparecer), no calcules con scroll: usa IntersectionObserver, que verás en APIs del navegador.
Abrir y cerrar ventanas
window.open("https://developer.mozilla.org", "_blank", "noopener");
Abre una URL en una pestaña nueva. Úsalo con moderación: los navegadores bloquean las ventanas emergentes que no vengan de un clic del usuario, y a casi nadie le gusta que se le abran pestañas solas. Para un enlace normal, es mejor un <a href="..." target="_blank"> en el HTML.
window.close() solo puede cerrar ventanas que haya abierto tu propio script; una pestaña que abrió el usuario no se deja cerrar.
location: la dirección actual
location contiene la URL de la página y la descompone en partes. Para la dirección https://www.tienda-montana.es:8080/catalogo/cuerdas?talla=60&color=rojo#opiniones:
| Propiedad | Valor |
|---|---|
href | La URL completa |
protocol | "https:" |
host | "www.tienda-montana.es:8080" (con el puerto) |
hostname | "www.tienda-montana.es" |
port | "8080" (vacío si es el puerto por defecto) |
pathname | "/catalogo/cuerdas" |
search | "?talla=60&color=rojo" (la cadena de consulta) |
hash | "#opiniones" (el fragmento) |
origin | "https://www.tienda-montana.es:8080" |
Redirigir y recargar
location.href = "/gracias.html"; // ir a otra página (queda en el historial)
location.assign("/gracias.html"); // lo mismo, escrito como método
location.replace("/login.html"); // ir sin dejar la actual en el historial
location.reload(); // recargar la página
La diferencia entre assign y replace está en el botón “Atrás”. Con replace, la página actual se sustituye en el historial, así que el usuario no puede volver a ella. Es lo que quieres tras un inicio de sesión o cuando una página antigua redirige a la nueva: si no, al pulsar “Atrás” volvería a la redirección y quedaría atrapado.
Si solo cambias el hash (location.hash = "#paso-2"), la página no se recarga: el navegador baja hasta el elemento con ese id y dispara el evento hashchange.
URL y URLSearchParams: leer y construir direcciones
Trocear una URL a mano con split("?") y split("&") es fácil de hacer mal: los espacios, los acentos o el símbolo & dentro de un valor van codificados (%20, %C3%A9…). Para eso existen dos clases.
URL analiza una dirección completa y te da las mismas partes que location:
const url = new URL("https://www.tienda-montana.es:8080/catalogo/cuerdas?talla=60#opiniones");
console.log(url.hostname); // www.tienda-montana.es
console.log(url.pathname); // /catalogo/cuerdas
console.log(url.hash); // #opiniones
Si el texto no es una URL completa, new URL lanza un TypeError. Puedes comprobarlo antes con URL.canParse(texto), o pasar una base como segundo argumento para resolver rutas relativas:
console.log(new URL("../ofertas", "https://tienda.es/catalogo/cuerdas").href);
// https://tienda.es/ofertas
URLSearchParams trabaja con la cadena de consulta. El caso más típico es leer los parámetros de la página actual:
// Imagina que la página es /buscar.html?q=botas+de+agua&pagina=2&marca=cimas&marca=valle
const params = new URLSearchParams(location.search);
console.log(params.get("q")); // botas de agua
console.log(Number(params.get("pagina"))); // 2
console.log(params.get("orden")); // null (no existe)
console.log(params.has("marca")); // true
console.log(params.getAll("marca")); // [ 'cimas', 'valle' ]
Fíjate en que get ya descodifica los valores (el + se convierte en espacio) y que siempre devuelve texto o null. Si esperas un número, conviértelo.
También puedes modificarlos y generar el texto de nuevo, ya codificado:
params.set("pagina", 3);
params.delete("marca");
params.append("orden", "precio");
console.log(params.toString()); // q=botas+de+agua&pagina=3&orden=precio
Y una URL tiene sus parámetros en url.searchParams, así que puedes construir direcciones seguras para fetch sin preocuparte de los caracteres raros:
const busqueda = new URL("https://ejemplo.es/buscar");
busqueda.searchParams.set("q", "café & té");
console.log(busqueda.href); // https://ejemplo.es/buscar?q=caf%C3%A9+%26+t%C3%A9
history: el historial de la pestaña
history.back(); // como pulsar "Atrás"
history.forward(); // como pulsar "Adelante"
history.go(-2); // dos páginas atrás
history.length; // cuántas entradas tiene el historial de esta pestaña
Por privacidad, no puedes leer qué páginas hay en el historial, solo moverte por él.
pushState: cambiar la URL sin recargar
Las SPA (Single Page Applications, aplicaciones de una sola página), como Gmail o las hechas con React o Vue, cambian el contenido con JavaScript sin recargar. Pero quieren que la URL cambie, para poder compartir un enlace o usar el botón “Atrás”. Lo consiguen con history.pushState:
history.pushState({ seccion: "perfil" }, "", "/perfil");
Recibe tres cosas: un objeto de estado que guardas con esa entrada, un título que los navegadores ignoran (pasa "") y la nueva URL. La barra de direcciones cambia a /perfil y se añade una entrada al historial, pero la página no se recarga ni se pide nada al servidor. Hay una variante, history.replaceState, que cambia la URL sin añadir entrada; es útil, por ejemplo, para reflejar en la URL los filtros de un buscador.
Cuando el usuario pulsa “Atrás” o “Adelante” entre entradas creadas así, se dispara el evento popstate con el estado guardado. Juntándolo todo tienes un enrutador mínimo:
const vista = document.querySelector("#vista");
const paginas = {
"/": "<h1>Inicio</h1>",
"/rutas": "<h1>Rutas de senderismo</h1>",
"/contacto": "<h1>Contacto</h1>",
};
function pintar(ruta) {
vista.innerHTML = paginas[ruta] ?? "<h1>Página no encontrada</h1>";
}
// Interceptar los clics en enlaces internos
document.addEventListener("click", (e) => {
const enlace = e.target.closest("a[data-interno]");
if (!enlace) return;
e.preventDefault();
history.pushState({}, "", enlace.getAttribute("href"));
pintar(location.pathname);
});
// Botones Atrás / Adelante
window.addEventListener("popstate", () => pintar(location.pathname));
pintar(location.pathname);
<nav>
<a href="/" data-interno>Inicio</a>
<a href="/rutas" data-interno>Rutas</a>
<a href="/contacto" data-interno>Contacto</a>
</nav>
<main id="vista"></main>
Cuidado:
pushStatesolo cambia la URL en el navegador. Si el usuario recarga en/rutaso entra desde un enlace compartido, el servidor recibirá una petición de/rutasy tiene que saber devolver tuindex.html. Por eso los servicios de hosting tienen una opción “SPA” o “rewrite to index.html”. Además,pushStateno funciona al abrir el archivo con doble clic (file://): usa un servidor local.
navigator: datos del navegador
navigator informa sobre el navegador y el dispositivo:
navigator.language; // "es-ES": idioma preferido del usuario
navigator.languages; // [ "es-ES", "es", "en" ]: todos, por orden
navigator.onLine; // true o false
El idioma te permite, por ejemplo, formatear fechas y números como espera el usuario o proponerle la versión de la web en su idioma.
Saber si hay conexión
function avisar() {
aviso.hidden = navigator.onLine;
}
window.addEventListener("online", avisar);
window.addEventListener("offline", avisar);
avisar();
navigator.onLine en false es fiable: seguro que no hay red. En true solo significa que hay alguna red (puede ser una wifi sin internet), así que gestiona igualmente los errores de fetch.
userAgent: con cautela
navigator.userAgent es un texto que identifica al navegador, del estilo "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/...". Verás en código antiguo cosas como if (navigator.userAgent.includes("MSIE")) para hacer una cosa en un navegador y otra en los demás.
No lo hagas. El texto es confuso (todos dicen ser “Mozilla”), se puede falsificar y los navegadores lo están recortando por privacidad. En su lugar, comprueba si existe la función que quieres usar. Esto se llama detección de características:
if ("share" in navigator) {
// el navegador permite compartir con el menú del sistema
botonCompartir.hidden = false;
}
if ("geolocation" in navigator) {
// se puede pedir la ubicación
}
navigator también es la puerta a otras APIs como el portapapeles (navigator.clipboard) o la ubicación (navigator.geolocation), que verás en APIs del navegador.
screen: la pantalla
screen.width; // ancho de la pantalla completa, en píxeles CSS
screen.height;
screen.availWidth; // sin la barra de tareas del sistema
screen.orientation.type; // "portrait-primary", "landscape-primary"...
Casi nunca lo necesitarás: lo que importa para tu diseño es el tamaño de la ventana (innerWidth), no el de la pantalla, porque la ventana puede no estar maximizada. Para adaptar el diseño, usa CSS.
alert, confirm y prompt: por qué evitarlos
window tiene tres funciones para mostrar cuadros de diálogo:
alert("Reserva guardada"); // solo un mensaje y "Aceptar"
const seguro = confirm("¿Borrar la reserva?"); // true o false
const nombre = prompt("¿Cómo te llamas?", "Ana"); // texto, o null si cancela
Son cómodas para hacer pruebas rápidas, pero en una web real tienen muchos problemas:
- Bloquean la página: mientras están abiertos, no se ejecuta nada más, ni siquiera animaciones o temporizadores.
- No se pueden personalizar: ni estilos, ni botones con otro texto, ni contenido con formato.
- Su aspecto depende del navegador y suelen parecer avisos del sistema, lo que genera desconfianza.
- Los navegadores pueden suprimirlos: tras varios seguidos ofrecen “Impedir que esta página cree más diálogos”, y en iframes de otro origen no aparecen.
La alternativa moderna es el elemento <dialog> de HTML, que puedes diseñar a tu gusto y abrir con dialog.showModal(). Lo tienes explicado en details y dialog. Para mensajes que no requieren respuesta, basta con mostrar un texto en la página.
Errores frecuentes
- Trocear la URL a mano:
location.search.split("=")[1]falla con varios parámetros, con acentos o con espacios. UsaURLSearchParams. - Olvidar que los parámetros son texto:
params.get("pagina") + 1da"21". Convierte conNumber(). - Usar
location.hreftras un login: el usuario puede volver con “Atrás” a la página de login. Usalocation.replace. - Recargar en una SPA y ver un 404: el servidor no conoce las rutas creadas con
pushState. Configúralo para devolverindex.html. - Detectar el navegador con
userAgent: comprueba si existe la función que necesitas ("share" in navigator). - Comparar
innerWidthen cadaresizepara cambiar el diseño: usa CSS omatchMedia. - Usar
alertpara avisar al usuario en una web terminada: usa<dialog>o un mensaje en la página.
Resumen
| Objeto | Para qué | Lo más usado |
|---|---|---|
window | La pestaña; objeto global | innerWidth, scrollY, scrollTo, matchMedia, open |
location | La URL actual | href, pathname, search, hash, replace, reload |
URL | Analizar o construir una URL | new URL(texto, base), url.searchParams |
URLSearchParams | La cadena de consulta | get, getAll, has, set, delete, toString |
history | El historial | back, pushState, replaceState, evento popstate |
navigator | El navegador | language, onLine, detección de características |
screen | La pantalla física | width, orientation (casi nunca hace falta) |
windowse puede omitir:locationeswindow.location.alert,confirmypromptbloquean la página; en webs reales, usa<dialog>.
En la siguiente lección aprenderás a ejecutar código más tarde o cada cierto tiempo: temporizadores.
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.