Guía práctica de versiones de JavaScript (ECMAScript)

Cuando hablamos de “versiones de JavaScript”, en realidad nos referimos a las ediciones del estándar ECMAScript (ES), mantenido por el comité TC39. Desde 2015 el estándar se publica todos los años (ES2015, ES2016, ES2017, etc.), incorporando mejoras en el lenguaje, nuevas APIs y correcciones. Esta guía te ayuda a entender la historia, el sistema de nombres y cómo aplicar todo esto en proyectos reales.

Conceptos clave

JavaScript vs. ECMAScript

Una de las primeras distinciones importantes es entender que JavaScript y ECMAScript no son exactamente lo mismo. JavaScript es el lenguaje que usamos a diario en navegadores, servidores (Node.js) y múltiples entornos. ECMAScript, en cambio, es la especificación técnica que define cómo debe comportarse el lenguaje para que exista compatibilidad entre todos los motores que lo implementan.

ECMAScript actúa como un “contrato” entre el estándar y los motores de ejecución. Cada motor —por ejemplo V8 (usado en Chrome y Node.js), SpiderMonkey (Firefox), JavaScriptCore (Safari) o Hermes (React Native)— toma esa especificación y la implementa, asegurando que cuando escribes código en un entorno, se comporte de la misma manera en otro (dentro de lo posible).

Esto explica por qué cuando aparecen nuevas versiones, se suele hablar en términos como “ES2015” o “ES2022”. Estas siglas hacen referencia al año de publicación de la especificación ECMAScript correspondiente, y por ende, a las capacidades del lenguaje que puedes usar en tu código. No significa que “JavaScript 2022” sea un producto aparte, sino que hace alusión al conjunto de características del estándar ECMAScript de ese año.

Además, ECMAScript es solo la base del lenguaje. Muchas APIs que usamos a diario —como el DOM, Fetch, WebSockets o IndexedDB— no forman parte de ECMAScript, sino de otros estándares web. Esto es importante porque puedes tener un entorno que implemente ECMAScript moderno pero no ciertas APIs del navegador (por ejemplo, en Node.js no existe window ni document).

Cómo se numeran las versiones

Antes de 2015, las versiones se nombraban con números sencillos: ES3 (1999) y ES5 (2009) fueron los más relevantes. Durante más de una década no hubo cambios grandes, y ES5 se convirtió en la base del JavaScript que dominó la web hasta mediados de la década de 2010. ES4 existió como borrador, pero fue abandonado debido a diferencias entre los miembros del comité técnico.

En 2015 ocurrió un cambio significativo. Se publicó una versión masiva conocida informalmente como ES6 y oficialmente como ES2015. A partir de entonces, el comité TC39 decidió adoptar un ciclo de lanzamientos anuales, con versiones incrementales llamadas por su año: ES2016, ES2017, ES2018… en adelante. Esto permitió:

  • Evitar esperar muchos años para incorporar nuevas funciones al lenguaje.
  • Introducir mejoras pequeñas y bien revisadas cada año, en lugar de grandes saltos que generan ruptura.
  • Dar más claridad a desarrolladores y herramientas respecto a qué año corresponde cada versión.

En la práctica, no se recomienda “elegir una versión específica” como si se tratara de un framework cerrado. Lo correcto es conocer qué características modernas soporta tu entorno y activarlas (por ejemplo, usando Babel, TypeScript o directamente configurando tu entorno de ejecución). De esta forma, puedes aprovechar las funcionalidades más recientes sin preocuparte por la etiqueta “ES2020” o “ES2022”.

 

Historia

ES3 y anteriores (1997–1999)

Las primeras versiones de ECMAScript sentaron las bases fundamentales de lo que hoy conocemos como JavaScript. ES1 (1997) y ES2 (1998) establecieron la sintaxis central del lenguaje, inspirada en Java y C, junto con su modelo de objetos basado en prototipos. Posteriormente, ES3 (1999) consolidó esas estructuras e introdujo mejoras clave como el manejo más robusto de cadenas, expresiones regulares, mejores excepciones y control de errores, así como la definición más clara del scope de variables. Estas versiones marcaron el inicio de la web dinámica y permitieron el auge de los primeros navegadores modernos.

ES5 (2009)

Tras una larga pausa de casi una década, ES5 llegó con el objetivo de estandarizar las buenas prácticas que los desarrolladores ya venían utilizando de forma no oficial. Introdujo el "strict mode" para fomentar un código más seguro y predecible, nuevos métodos para trabajar con arrays (forEach, map, filter, reduce), el método Object.defineProperty para un control más fino de las propiedades de objetos, y soporte nativo para JSON, que hasta ese momento dependía de librerías externas. ES5 se convirtió en la base estable para toda la década siguiente y es el estándar mínimo que todavía utilizan muchos navegadores antiguos.

ES2015 / ES6 (2015)

Conocida como la versión que modernizó radicalmente el lenguaje, ES2015 introdujo una gran cantidad de funcionalidades que cambiaron por completo la forma en que se escribe JavaScript:

  • Variables modernas: let y const, reemplazando muchos usos de var.
  • Clases y módulos: class para una sintaxis más clara orientada a objetos, y import/export para modularizar el código de forma nativa.
  • Funciones y sintaxis más expresiva: arrow functions, template strings y destructuring para simplificar operaciones comunes.
  • Nuevas estructuras de datos: Map, Set y Symbol para ampliar las posibilidades del lenguaje.
  • Promesas y asincronía: Promise permitió escribir flujos asíncronos más claros que con callbacks anidados.
  • Rest y spread operators: facilitaron la manipulación flexible de arrays y objetos.

ES2015 marcó un antes y un después: dio origen a la era moderna de JavaScript, impulsó el ecosistema de herramientas (Babel, Webpack, TypeScript) y permitió que el lenguaje evolucionara sin perder compatibilidad.

ES2016 en adelante (ciclo anual)

A partir de ES2016, el comité TC39 adoptó un modelo de lanzamientos anuales. Esto significó dejar atrás los grandes saltos y adoptar mejoras graduales, enfocadas en resolver necesidades reales sin interrumpir el ecosistema existente. Cada versión introduce un pequeño conjunto de características bien definidas, como:

  • Nuevos operadores y mejoras de sintaxis (**, ?., ??).
  • Métodos utilitarios en objetos y arrays (includes, flat, Object.entries, Object.fromEntries).
  • Mejoras en asincronía (async/await, Promise.finally, Promise.allSettled, Promise.any).
  • Nuevas APIs y patrones de programación que se adaptan a las tendencias actuales (BigInt, campos privados en clases, top-level await).

Este enfoque incremental ha permitido que JavaScript evolucione de forma constante y compatible, manteniéndose relevante en navegadores, servidores y múltiples plataformas sin grandes rupturas entre versiones.

Cómo se numeran las versiones

Hasta 2015 se usaban números “ES3, ES5”. Con la gran actualización de 2015 se adoptó el esquema anual: ES2015 (también llamado ES6), ES2016, ES2017… En la práctica, la recomendación no es “usar ES2020” como un paquete cerrado, sino habilitar las features modernas que tu entorno soporte.

 

Características destacadas por edición (selección útil)

ES2016: Operador de potencia ** y Array.prototype.includes.

ES2017: async/await, Object.values/Object.entries, String.prototype.padStart/padEnd.

ES2018: Rest/spread en objetos, mejoras en RegExp, Promise.prototype.finally.

ES2019: Array.prototype.flat/flatMap, Object.fromEntries, trimStart/trimEnd, catch opcional.

ES2020: BigInt, operador de encadenamiento opcional ?., nullish coalescing ??, Promise.allSettled, import() dinámico.

ES2021: String.prototype.replaceAll, operadores lógicos de asignación &&= ||= ??=, Promise.any.

ES2022: Campos y métodos privados de clase #, at() para arrays/strings, top-level await en módulos.

ES2023–ES2025 (ejemplos recientes): Mejoras continuas a colecciones, métodos de copia inmutable, afinamiento de clases y patrones de asincronía. La idea es que cada año recibas mejoras pequeñas pero útiles, sin saltos traumáticos.

 

Compatibilidad y herramientas

Transpilación y polyfills

Si necesitas escribir con sintaxis moderna pero ejecutar en entornos antiguos, usa un transpilador (p. ej. Babel, TypeScript) y aplica polyfills para APIs faltantes. La configuración típica apunta a un conjunto de navegadores objetivo o versiones mínimas de Node.js, generando solo lo necesario para tu público.

Detectar soporte de features

Haz feature detection antes de usar cierta API. Por ejemplo:

// ¿Soporta Optional Chaining?
try {
  const ok = ({a:{b:1}})?.a?.b === 1;
  console.log('Encadenamiento opcional:', ok);
} catch (e) {
  console.log('No soportado en este entorno');
}

En navegador también puedes consultar documentación de compatibilidad y, en Node.js, revisar la versión del runtime y su tabla de soportes.

 

Módulos y ecosistema

ESM vs CommonJS

En JavaScript moderno, los módulos son la forma estándar de organizar y reutilizar código. En lugar de tener todo en un solo archivo, puedes dividir tus funcionalidades en múltiples archivos, importando y exportando solo lo necesario. Existen dos sistemas principales de módulos: ES Modules (ESM) y CommonJS (CJS).

ES Modules (ESM) es el estándar oficial de ECMAScript, adoptado en navegadores modernos y en Node.js a partir de la versión 12 (de forma estable desde la 14 en adelante). Se basa en las palabras clave import y export, y permite una sintaxis declarativa, estática y fácil de analizar para herramientas como bundlers o linters.

Por otro lado, CommonJS fue el sistema predominante en el ecosistema de Node.js durante muchos años, antes de que ESM fuera implementado nativamente. Usa funciones require() para importar y module.exports para exportar. Aunque sigue presente por compatibilidad, hoy en día se recomienda migrar progresivamente a ESM, ya que:

  • ESM es compatible tanto en frontend como en backend, evitando tener dos sistemas distintos.
  • Permite análisis estático, lo que hace posible tree-shaking (eliminación automática de código no usado) y optimizaciones de rendimiento.
  • Es el estándar oficial a futuro, lo que asegura soporte en nuevas plataformas y herramientas.
Cómo habilitar ESM en Node.js

Por defecto, Node.js asume que tu proyecto usa CommonJS. Para activar ESM de forma nativa debes añadir en tu package.json lo siguiente:

{
  "type": "module"
}

Esto indica a Node.js que los archivos .js deben tratarse como módulos ESM. A partir de ese momento puedes usar import y export directamente, sin necesidad de transpilar.

Diferencias clave entre ESM y CommonJS
  • Sintaxis: ESM usa import/export; CommonJS usa require()/module.exports.
  • Evaluación: ESM es estático (las importaciones se resuelven en tiempo de carga), mientras que CommonJS es dinámico (las importaciones se ejecutan en tiempo de ejecución).
  • Scope: Cada módulo tiene su propio ámbito. En CommonJS, las exportaciones son un objeto mutable, mientras que en ESM las exportaciones son vinculaciones inmutables (bindings), lo que permite mayor seguridad y optimización.
  • Extensiones: En ESM debes incluir siempre la extensión del archivo (ej. './math.js'), mientras que en CommonJS se podía omitir.
Ejemplos prácticos

ESM (estándar actual):

// math.js
export function suma(a, b) {
  return a + b;
}

// main.js
import { suma } from './math.js';
export const resultado = suma(1, 2); 

CommonJS (legacy en Node.js):

// math.cjs
function suma(a, b) {
  return a + b;
}
module.exports = { suma };

// main.cjs
const { suma } = require('./math.cjs');
const resultado = suma(1, 2);
module.exports = { resultado }; 
Uso en frontend

En el navegador, ESM es soportado de forma nativa por todos los navegadores modernos. Solo debes usar el atributo type="module" en la etiqueta

Para proyectos más grandes, es común usar bundlers como Vite, esbuild o Webpack. Estos agrupan todos tus módulos en uno o varios archivos optimizados, mejorando el rendimiento y la compatibilidad con navegadores más antiguos.

Convivencia de ambos sistemas

Durante la transición hacia ESM, es habitual encontrar proyectos que usan ambos sistemas. Node.js permite importar módulos CommonJS en un entorno ESM mediante createRequire, y viceversa usando import() dinámico. No obstante, se recomienda definir un sistema principal y evitar mezclar ambos salvo que sea estrictamente necesario.

En resumen, ESM es el presente y futuro del ecosistema JavaScript. Adoptarlo te permite escribir código más limpio, interoperable y preparado para las optimizaciones modernas.

 

Estrategia práctica para proyectos

Navegadores

Al desarrollar para la web, es fundamental definir claramente tus “browsers targets”, es decir, los navegadores y versiones mínimas que vas a soportar oficialmente. Esta decisión impacta directamente en el peso del bundle, la necesidad de polyfills y el uso de características modernas del lenguaje.

Una buena práctica es apuntar a navegadores modernos por defecto y aplicar estrategias progresivas para casos especiales. Por ejemplo:

  • Usa ES Modules nativos en el frontend siempre que sea posible, evitando generar bundles pesados para navegadores actuales.
  • Implementa carga diferida (lazy loading) para módulos no esenciales, optimizando tiempos de carga inicial.
  • Aplica polyfills graduales: incluye solo las APIs faltantes según tus browsers targets, en lugar de cargar polyfills globales enormes.

Herramientas como browserslist o babel-preset-env te permiten automatizar este proceso. Definiendo tus targets en un solo lugar, tu build se ajustará automáticamente al nivel de compatibilidad necesario, sin añadir código legacy innecesario.

Node.js

En backend, la estrategia pasa por apuntar siempre a una versión LTS (Long Term Support) reciente. Esto ofrece múltiples beneficios:

  • Mayor estabilidad y soporte extendido.
  • Acceso nativo a la mayoría de las características modernas del lenguaje (sin necesidad de transpilar).
  • Mejor rendimiento y seguridad actualizada.

Adoptar ESM como formato principal es recomendable siempre que no tengas restricciones de interoperabilidad con módulos CommonJS. Esto simplifica el uso de imports modernos, te permite beneficiarte de herramientas más inteligentes y reduce configuraciones innecesarias. Además, mantener actualizado tu runtime evita dependencias obsoletas y facilita el mantenimiento a largo plazo.

Librerías y SDKs

Si desarrollas librerías o SDKs para que otros proyectos las consuman, debes pensar en compatibilidad y flexibilidad:

  • Ofrece salida dual (ESM + CommonJS) cuando sea posible. Esto permite que tanto proyectos modernos como legacy puedan integrarse sin problemas.
  • Define claramente tus exports en package.json, para que los bundlers y Node.js puedan resolver correctamente las versiones adecuadas.
  • Documenta las versiones mínimas de runtime (navegadores y Node.js) que soportas. Esto evita confusiones y facilita que los consumidores integren tu librería correctamente.
  • Si tu librería usa características muy modernas, considera proveer una versión transpilada para maximizar compatibilidad.

En resumen, una estrategia sólida implica aprovechar al máximo las capacidades modernas sin comprometer compatibilidad. Define tus targets desde el principio, mantén tus entornos actualizados y distribuye tus paquetes de forma clara para adaptarte a distintos escenarios de consumo.

Whatsapp Mentores Tech