Mejores Prácticas en JavaScript Moderno - Parte 1


JavaScript es, sin duda, el lenguaje de programación más utilizado en el mundo y tiene una enorme influencia en una de las tecnologías más importantes de nuestra vida diaria: internet. Con este poder viene una gran responsabilidad, y el ecosistema de JavaScript ha estado evolucionando rápidamente, haciendo difícil mantenerse al día con las mejores prácticas.

En este artículo, exploraremos algunas de las principales mejores prácticas en JavaScript moderno para escribir un código más limpio, mantenible y eficiente.


1. Las reglas del proyecto son lo más importante

Cada proyecto puede tener reglas específicas para mantener la coherencia del código. Estas reglas siempre tendrán prioridad sobre cualquier recomendación externa, incluso sobre las de este artículo. Antes de implementar una práctica en un proyecto, asegúrate de que esté alineada con las reglas establecidas y que todos los miembros del equipo estén de acuerdo.


2. Usa JavaScript actualizado

JavaScript ha evolucionado enormemente desde su creación en 1995. Muchas prácticas antiguas que encuentras en internet pueden estar desactualizadas. Antes de implementar una técnica, verifica que sea compatible con la versión actual del lenguaje.

Además, si decides usar características de JavaScript muy recientes, asegúrate de que estén en Ecma TC39 Stage 3 o superior para garantizar su estabilidad.


3. Usa let y const en lugar de var

Aunque var sigue siendo válido, su uso es considerado obsoleto y, en muchos casos, puede introducir errores difíciles de rastrear debido a su alcance funcional (o “hoisting”). En cambio, let y const nos brindan un alcance más predecible y seguro, al estar limitados al bloque donde se declaran.

¿Cuándo usar let y cuándo usar const?

  • Usa const siempre que una variable no cambie su referencia o valor. Esto hace que tu código sea más fácil de entender y reduce errores al proteger valores inmutables.
  • Usa let si necesitas reasignar el valor de la variable en el futuro, pero solo en los casos en que sea necesario.

💡 Regla de oro: Usa const por defecto. Si más adelante necesitas cambiar el valor, cambia a let.


Ejemplos prácticos

  1. const para valores constantes:
const PI = 3.14159;
console.log(PI); // 3.14159

PI = 3; // TypeError: Assignment to constant variable.
  1. let para valores que cambian:
let count = 0;

for (let i = 1; i <= 3; i++) {
  count += i; 
}
console.log(count); // 6
  1. Comparativa del alcance (let vs var):
if (true) {
  let blockScoped = "Solo dentro del bloque";
  var functionScoped = "Disponible fuera también";
}

console.log(functionScoped); // "Disponible fuera también"
console.log(blockScoped); // ReferenceError: blockScoped is not defined
  1. Evita problemas con bucles y callbacks:

Con var puedes tener comportamientos inesperados en bucles, sobre todo en funciones asincrónicas:

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // Imprime 3, 3, 3
}

Mientras que let soluciona esto:

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // Imprime 0, 1, 2
}

Reemplazar var con let y const no solo es una buena práctica, sino que también ayuda a que tu código sea más seguro, más legible y más fácil de depurar. Haz que el futuro tú te lo agradezca.


4. Opta por Clases: Simplicidad en JavaScript

El uso de Function.prototype para la programación orientada a objetos en JavaScript es un enfoque más antiguo y a menudo propenso a errores. Por el contrario, las clases introducidas en ES6 ofrecen una sintaxis más intuitiva y cercana a otros lenguajes orientados a objetos, facilitando la legibilidad y el mantenimiento del código.

Ejemplo con clases (moderno y claro):

class Persona {
  constructor(nombre) {
    this.nombre = nombre;
  }

  obtenerNombre() {
    return this.nombre;
  }
}

const persona = new Persona('Juan');
console.log(persona.obtenerNombre()); // 'Juan'

Comparación con Function.prototype (complicado y menos intuitivo):

function Persona(nombre) {
  this.nombre = nombre;
}

Persona.prototype.obtenerNombre = function () {
  return this.nombre;
};

const persona = new Persona('Juan');
console.log(persona.obtenerNombre()); // 'Juan'

Como puedes ver, el enfoque basado en prototype requiere más pasos para definir métodos y puede resultar más confuso para desarrolladores menos experimentados. Las clases no solo son más fáciles de leer, sino que también promueven un código más limpio y modular.

¿Por qué usar clases?

  • Más legibles y menos propensas a errores.
  • Facilitan la herencia con extends y el uso de super.
  • Más compatibles con herramientas modernas y estándares de ES6+.

5. Usa Campos Privados Reales en JavaScript

Durante mucho tiempo, los desarrolladores de JavaScript usaron convenciones como un guion bajo (_) para simular campos privados en las clases. Sin embargo, esto era solo una convención visual, ya que las propiedades seguían siendo accesibles desde fuera de la clase. Ahora, gracias a los campos privados reales, podemos garantizar que ciertas propiedades sean completamente inaccesibles desde el exterior.

⚠️ Atención: Esta característica puede que no este disponible en la consola de algunos navegadores.

¿Por qué usar campos privados reales?

  1. Encapsulación auténtica: Protegen tus datos y aseguran que no puedan ser accedidos ni modificados fuera del contexto de la clase.
  2. Legibilidad: Al usar el prefijo #, queda claro qué propiedades son privadas, mejorando la comprensión del código.
  3. Seguridad de datos: Evitan errores accidentales o accesos no intencionados a propiedades internas.

Ejemplo básico:

class Persona {
  #nombre; // Campo privado

  constructor(nombre) {
    this.#nombre = nombre;
  }

  obtenerNombre() {
    return this.#nombre;
  }
}

const persona = new Persona('Juan');
console.log(persona.obtenerNombre()); // 'Juan'

// El siguiente código lanzará un error porque el campo es privado
console.log(persona.#nombre); // SyntaxError: Private field '#nombre' must be declared in an enclosing class

Ejemplo avanzado: contadores protegidos

Imagina que quieres crear una clase que registre el número de visitas a una página, pero no deseas que nadie pueda manipular ese contador directamente.

class Pagina {
  #visitas; // Campo privado

  constructor() {
    this.#visitas = 0;
  }

  registrarVisita() {
    this.#visitas++;
  }

  obtenerVisitas() {
    return this.#visitas;
  }
}

const pagina = new Pagina();
pagina.registrarVisita();
pagina.registrarVisita();
console.log(pagina.obtenerVisitas()); // 2

// Intentar acceder al contador desde fuera genera un error
console.log(pagina.#visitas); // SyntaxError: Private field '#visitas' must be declared in an enclosing class

En este caso, el contador #visitas está completamente protegido de accesos externos, lo que garantiza que su valor no sea manipulado de manera indebida.

Consideraciones

  • Los campos privados no pueden ser accedidos ni por subclases.
  • Si necesitas interactuar con datos privados en herencias, considera usar métodos protegidos (protected) en lugar de campos privados.

6. Usa funciones flecha

Las funciones flecha son una forma moderna y elegante de escribir funciones en JavaScript. Ofrecen una sintaxis más corta y, a diferencia de las funciones tradicionales, heredan automáticamente el contexto de this, lo que evita problemas comunes en programación orientada a objetos.

Son especialmente útiles en funciones de orden superior como map, filter y reduce, donde necesitamos pasar funciones como argumentos.

¿Por qué usar funciones flecha?

  1. Sintaxis más corta y limpia: Ideal para mantener el código más legible.
  2. Contexto de this automático: Perfecto para evitar errores en callbacks o métodos.
  3. Uso ideal en funciones inline: Como las que utilizamos en map, filter o eventos.

Ejemplos prácticos

1. Con map para transformar arrays
const numeros = [1, 2, 3];
const dobles = numeros.map(num => num * 2);
console.log(dobles); // [2, 4, 6]
2. Con filter para filtrar elementos
const edades = [15, 22, 30, 12];
const mayoresDeEdad = edades.filter(edad => edad >= 18);
console.log(mayoresDeEdad); // [22, 30]
3. Con reduce para sumar valores
const precios = [10, 20, 30];
const total = precios.reduce((acumulado, precio) => acumulado + precio, 0);
console.log(total); // 60
4. En eventos DOM (¡cuidado con el contexto!)

Cuando usamos funciones flecha en eventos, el contexto this no cambiará, lo cual puede ser útil:

class Boton {
  constructor(elemento) {
    this.elemento = elemento;
    this.elemento.addEventListener('click', () => {
      console.log(`¡Click detectado en ${this.elemento.id}!`);
    });
  }
}

const miBoton = new Boton(document.getElementById('miBoton'));

Consejo: cuándo no usarlas

Aunque las funciones flecha son geniales, no son ideales para todo. Evítalas en casos donde necesites acceder al propio contexto de la función, como en funciones que utilizan this dinámico o si necesitas escribir métodos en prototipos.

Ejemplo donde una función normal es mejor:

const objeto = {
  nombre: "Ejemplo",
  imprimir() {
    console.log(this.nombre); // "Ejemplo"
  }
};

objeto.imprimir();

Si cambiaras imprimir a una función flecha, perderías el contexto de this.


¡Vamos a mejorar esa sección! Agregué algo de contexto, una explicación más clara, y algunos ejemplos adicionales para que sea más completa y útil.


7. Operador de coalescencia nula (??)

El operador de coalescencia nula (??) es una alternativa más precisa al operador lógico || para asignar valores por defecto. Mientras que || considera “falsy” a valores como 0, false o "", el operador ?? solo evalúa null o undefined como valores “ausentes”. Esto lo convierte en una opción más segura y específica en muchos casos.

¿Cuál es la diferencia clave?

Con ||, cualquier valor “falsy” será reemplazado por el valor por defecto.

Con ??, únicamente se reemplazan los valores que son null o undefined. Esto te permite mantener valores “falsy” como 0 o "" si son válidos en tu contexto.

Ejemplo básico:

const valor = 0;
const resultado = valor ?? 10;
console.log(resultado); // 0, porque `0` no es `null` ni `undefined`

En cambio, con ||:

const valor = 0;
const resultado = valor || 10;
console.log(resultado); // 10, porque `0` es "falsy"

Casos prácticos con ??

  1. Establecer valores predeterminados sin sobrescribir valores válidos:
const usuario = {
  nombre: '',
  edad: 0,
};

// Usando ?? para mantener valores válidos
const nombre = usuario.nombre ?? 'Anónimo';
const edad = usuario.edad ?? 18;

console.log(nombre); // ''
console.log(edad); // 0
  1. Validar configuración opcional:

Supongamos que tienes un sistema que permite personalizar opciones:

function configurar(opciones) {
  const tema = opciones.tema ?? 'claro';
  const idioma = opciones.idioma ?? 'es';
  console.log(`Tema: ${tema}, Idioma: ${idioma}`);
}

configurar({ tema: 'oscuro' }); 
// Tema: oscuro, Idioma: es
configurar({ idioma: 'en' });  
// Tema: claro, Idioma: en
  1. Evitar errores al trabajar con propiedades opcionales:
const configuracion = null;
const modo = configuracion?.modo ?? 'por defecto';
console.log(modo); // 'por defecto'
- Escrito por Juan Beresiarte -