Saltar al contenido
bom.js · devschool

El BOM: window, location, history y navigator en JavaScript

Lección 33 de 43 · 12 min de lectura · Actualizado el

En esta lección
  1. Qué es el BOM
  2. window: la ventana
  3. location: la dirección actual
  4. URL y URLSearchParams: leer y construir direcciones
  5. history: el historial de la pestaña
  6. navigator: datos del navegador
  7. screen: la pantalla
  8. alert, confirm y prompt: por qué evitarlos
  9. Errores frecuentes
  10. Resumen

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 window ni location; el objeto global allí es globalThis (que en el navegador también apunta a window).

window: la ventana

Tamaño de la ventana

PropiedadQué mide
window.innerWidth / innerHeightÁrea visible de la página, en píxeles (con la barra de scroll)
window.outerWidth / outerHeightLa ventana completa, con barras y bordes del navegador
document.documentElement.clientWidthAncho 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:

PropiedadValor
hrefLa 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: pushState solo cambia la URL en el navegador. Si el usuario recarga en /rutas o entra desde un enlace compartido, el servidor recibirá una petición de /rutas y tiene que saber devolver tu index.html. Por eso los servicios de hosting tienen una opción “SPA” o “rewrite to index.html”. Además, pushState no funciona al abrir el archivo con doble clic (file://): usa un servidor local.

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. Usa URLSearchParams.
  • Olvidar que los parámetros son texto: params.get("pagina") + 1 da "21". Convierte con Number().
  • Usar location.href tras un login: el usuario puede volver con “Atrás” a la página de login. Usa location.replace.
  • Recargar en una SPA y ver un 404: el servidor no conoce las rutas creadas con pushState. Configúralo para devolver index.html.
  • Detectar el navegador con userAgent: comprueba si existe la función que necesitas ("share" in navigator).
  • Comparar innerWidth en cada resize para cambiar el diseño: usa CSS o matchMedia.
  • Usar alert para avisar al usuario en una web terminada: usa <dialog> o un mensaje en la página.

Resumen

ObjetoPara quéLo más usado
windowLa pestaña; objeto globalinnerWidth, scrollY, scrollTo, matchMedia, open
locationLa URL actualhref, pathname, search, hash, replace, reload
URLAnalizar o construir una URLnew URL(texto, base), url.searchParams
URLSearchParamsLa cadena de consultaget, getAll, has, set, delete, toString
historyEl historialback, pushState, replaceState, evento popstate
navigatorEl navegadorlanguage, onLine, detección de características
screenLa pantalla físicawidth, orientation (casi nunca hace falta)
  • window se puede omitir: location es window.location.
  • alert, confirm y prompt bloquean 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).

bom.html
Resultado

Pon a prueba lo que has aprendido

[JavaScript] Estás en https://tienda.es/catalogo?talla=60#opiniones. ¿Qué devuelve location.search?

[JavaScript] Con la URL ?pagina=2, ¿qué da new URLSearchParams(location.search).get("pagina") + 1?

[JavaScript] Tras iniciar sesión quieres redirigir a otra página sin que "Atrás" vuelva al login. ¿Qué usas?

[JavaScript] ¿Por qué se desaconseja alert(), confirm() y prompt() en una web terminada?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.