
15 Dicas de JavaScript
Que todo desarrollador web debería dominar
¡Hola Devs! ¿Todo tranquilo? Si eres como yo, ya debes haber pasado horas mirando un código JavaScript intentando entender por qué diablos esa función no está funcionando como debería. O quizás hayas gritado al monitor "¡¿POR QUÉ ESTÁS UNDEFINED?!". ¡Bienvenido al club!
JavaScript es como un amigo lleno de personalidad: a veces nos sorprende para bien, a veces nos deja completamente confusos. Pero, con algunos consejos y trucos bajo la manga, pueden convertirse en mejores amigos. ¡Vamos a ello!
1. Entiende (de verdad) el scope de las variables
El scope en JavaScript puede ser traicionero. Mucha gente todavía tropieza con la diferencia entre var
, let
y const
.
La forma INCORRECTA:
function badExample() {
// Usando var que tiene scope de función
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // ¿Qué crees que va a imprimir?
}, 100);
}
}
badExample(); // Imprime: 5, 5, 5, 5, 5
¡Sorpresa! ¿Esperabas 0, 1, 2, 3, 4? Pues, var
tiene scope de función, no de bloque. Cuando el setTimeout se ejecuta, el ciclo ya ha terminado e i
vale 5.
La forma CORRECTA:
function goodExample() {
// Usando let que tiene scope de bloque
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // ¡Ahora sí!
}, 100);
}
}
goodExample(); // Imprime: 0, 1, 2, 3, 4
Con let
, cada iteración del ciclo crea un nuevo enlace para i
. ¡Maravilla!
2. Deja de abusar del this
El this
en JavaScript es como un GPS que cambia de opinión según la calle en la que estés. Mucha gente pierde horas de vida intentando entender hacia dónde está apuntando.
La forma INCORRECTA:
const object = {
value: 42,
showLater: function() {
setTimeout(function() {
console.log(this.value); // ¿Qué es "this" aquí?
}, 1000);
}
};
object.showLater(); // Imprime: undefined
Ué, ¿dónde está el 42? El problema es que dentro de la función de callback del setTimeout, this
ya no es el objeto, sino el objeto global (o undefined en modo estricto).
La forma CORRECTA:
const object = {
value: 42,
showLater: function() {
// Opción 1: Arrow function
setTimeout(() => {
console.log(this.value); // "this" es capturado del contexto externo
}, 1000);
// Opción 2: Capturar "this" en una variable
const self = this;
setTimeout(function() {
console.log(self.value);
}, 1000);
// Opción 3: Usar bind
setTimeout(function() {
console.log(this.value);
}.bind(this), 1000);
}
};
object.showLater(); // Imprime: 42
Personalmente, prefiero las arrow functions. Son más elegantes y eliminan la necesidad de estar siempre intentando "sujetar" el this
en variables.
3. Aprende a amar los métodos modernos de arrays
Los bucles for
tradicionales son como tu amigo del instituto: tienen historia juntos, pero hay personas mucho más interesantes por conocer.
La forma INCORRECTA:
const numbers = [1, 2, 3, 4, 5];
const evens = [];
// Bucle tradicional para filtrar números pares
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
evens.push(numbers[i]);
}
}
// Bucle tradicional para duplicar valores
const doubled = [];
for (let i = 0; i < evens.length; i++) {
doubled.push(evens[i] * 2);
}
console.log(doubled); // [4, 8]
¿Funciona? Sí. ¿Es elegante? No mucho.
La forma CORRECTA:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers
.filter(num => num % 2 === 0) // Filtrar pares
.map(num => num * 2); // Duplicar valores
console.log(doubled); // [4, 8]
¡Mucho más limpio y expresivo! Los métodos modernos como map
, filter
, reduce
, find
y forEach
son como superpoderes para quien trabaja con arrays. Además, es mucho más fácil leer y entender lo que el código está haciendo.
4. Desestructuración: tu mejor amigo para objetos y arrays
Si todavía estás accediendo a propiedades de objetos y elementos de arrays a la antigua usanza, es hora de conocer la desestructuración.
La forma INCORRECTA:
function showUser(user) {
const name = user.name;
const email = user.email;
const age = user.age;
console.log('Nombre: ' + name);
console.log('Email: ' + email);
console.log('Edad: ' + age);
}
const user = {
name: 'João',
email: 'joao@exemplo.com',
age: 30
};
showUser(user);
La forma CORRECTA:
function showUser({ name, email, age }) {
console.log(`Nombre: ${name}`); // ¡Las template strings también son increíbles!
console.log(`Email: ${email}`);
console.log(`Edad: ${age}`);
}
const user = {
name: 'João',
email: 'joao@exemplo.com',
age: 30
};
showUser(user);
// Bonus: desestructuración de arrays
const rgb = [255, 128, 0];
const [red, green, blue] = rgb;
console.log(`R: ${red}, G: ${green}, B: ${blue}`);
La desestructuración hace que tu código sea más limpio y menos verboso. Es como esa herramienta multiusos que no sabías que necesitabas hasta que empezaste a usarla.
5. Promesas y async/await: ¡adiós al callback hell!
Si todavía estás anidando callbacks como si estuvieras construyendo una muñeca rusa, ¡es hora de cambiar!
La forma INCORRECTA (Callback Hell):
function searchData() {
searchUser(function (user) {
searchOrders(user.id, function (orders) {
searchDetails(orders[0].id, function (details) {
showResult(user, orders, details, function () {
console.log('¡Finalmente terminé!');
// ¿Y si necesitamos añadir otra capa más?
// 😱
});
});
});
});
}
¡Uy, eso dolió a la vista! Este patrón es tan infame que se ganó el apodo de "callback hell" o "pyramid of doom".
La forma CORRECTA con Promesas:
function searchData() {
return fetchUser()
.then(user => {
return fetchOrders(user.id)
.then(orders => {
return fetchDetails(orders[0].id)
.then(details => {
return showResult(user, orders, details);
});
});
})
.then(() => console.log('¡Finalmente terminé!'))
.catch(error => console.error('Ups, algo salió mal:', error));
}
Mejor, pero todavía tenemos anidamiento. ¡Vamos a mejorar más!
La forma AÚN MEJOR con async/await:
async function fetchData() {
try {
const user = await searchUser();
const orders = await searchOrders(user.id);
const details = await searchDetails(orders[0].id);
await displayResult(user, orders, details);
console.log('¡Finalmente terminé!');
} catch (error) {
console.error('Ups, algo salió mal:', error);
}
}
¡Ahora sí! El código se vuelve casi tan lineal como el código síncrono normal, pero mantiene todas las ventajas de la asincronicidad.
6. Utiliza los operadores lógicos de forma inteligente
Los operadores lógicos en JavaScript son mucho más poderosos de lo que parecen a primera vista.
La forma INCORRECTA:
function greet(user) {
let message;
if (user && user.name) {
message = 'Hola, ' + user.name;
} else {
message = 'Hola, visitante';
}
return message;
}
La forma CORRECTA:
function greet(user) {
return `Hola, ${user?.name || 'visitante'}`;
}
El operador de encadenamiento opcional ?.
(introducido en ES2020) verifica si user
existe antes de intentar acceder a la propiedad name
. Combinado con el operador ||
para proporcionar un valor por defecto, ¡tenemos una línea bastante elegante!
7. Utiliza Módulos para Organizar tu Código
Tener todo el código en un único archivo gigante es como tener todo tu guardarropa apilado en un solo cajón. Funciona, ¡pero es caótico!
La forma INCORRECTA:
// giant-file.js con 1000+ líneas de código
function validateEmail(email) { /* ... */ }
function validatePassword(password) { /* ... */ }
function formatDate(date) { /* ... */ }
function calculateTotal(items) { /* ... */ }
// ... 100 funciones más ...
// Utilizando las funciones
const validEmail = validateEmail('user@example.com');
La forma CORRECTA:
// validation.js
export function validateEmail(email) { /* ... */ }
export function validatePassword(password) { /* ... */ }
// formatting.js
export function formatDate(date) { /* ... */ }
// calculator.js
export function calculateTotal(items) { /* ... */ }
// main.js
import { validateEmail } from './validation.js';
import { formatDate } from './formatting.js';
const validEmail = validateEmail('user@example.com');
const formattedDate = formatDate(new Date());
Además de hacer el código más organizado, los módulos ofrecen encapsulación y evitan contaminar el scope global.
8. Maneja Correctamente los Números
JavaScript tiene algunas... particularidades cuando se trata de números que pueden pillarte desprevenido.
La forma INCORRECTA:
const result = 0.1 + 0.2;
console.log(result === 0.3); // false (!)
console.log(result); // 0.30000000000000004
// Otro problema común:
console.log(1000000000000000 === 1000000000000001); // true (!) - Imprecisión en números grandes
La forma CORRECTA:
// Para comparaciones
function approximatelyEqual(a, b, epsilon = 0.0001) {
return Math.abs(a - b) < epsilon;
}
console.log(approximatelyEqual(0.1 + 0.2, 0.3)); // verdadero
// Para cálculos financieros, utiliza una biblioteca o técnicas específicas
function addMoney(a, b) {
return (a * 100 + b * 100) / 100;
}
console.log(addMoney(0.1, 0.2)); // 0.3
En situaciones en las que necesitas alta precisión, considera utilizar bibliotecas como decimal.js
o big.js
.
9. Closure: La Técnica Secreta de los Grandes Maestros
Closure es un concepto poderoso que permite encapsular datos y crear funciones con "memoria".
La forma INCORRECTA:
let counter = 0;
function increment() {
counter++;
return counter;
}
// ¿El problema? Cualquier código puede modificar contador
counter = 100; // ¡Ups, alguien modificó nuestra variable!
console.log(increment()); // 101
La forma CORRECTA:
function createCounter() {
let counter = 0;
return function () {
counter++;
return counter;
};
}
const increment = createCounter();
const increment2 = createCounter(); // Un contador separado
console.log(increment()); // 1
console.log(increment()); // 2
console.log(increment2()); // 1 (independiente)
// ¡Nadie puede acceder o modificar la variable contador directamente!
Las closures son como pequeñas cápsulas de datos a las que solo pueden acceder funciones específicas. Esto permite crear variables privadas y mantener el estado entre llamadas a funciones.
10. Aprende a Manejar Eventos y el DOM
Manipular el DOM es una de las tareas más comunes para los desarrolladores web, pero también una de las más susceptibles a problemas.
La forma INCORRECTA:
// Añadir eventos directamente en elementos HTML (en HTML)
<button onclick="myFunction()">Clic</button>
// O añadiendo con addEventListener repetidamente
document.querySelectorAll('.button').forEach(button => {
button.addEventListener('click', function () {
// Código duplicado para cada botón
alert('¡Botón pulsado!');
});
});
La forma CORRECTA:
// Delegación de eventos - ¡Más eficiente!
document.getElementById('buttons-container').addEventListener('click', function (e) {
// Verifica si el clic fue en un botón
if (e.target.matches('.button')) {
alert('¡Botón pulsado!');
}
});
// Mejor aún, separar el HTML del JavaScript
// HTML: <div id="myButton" class="button">Clic</div>
// JavaScript:
document.getElementById('myButton').addEventListener('click', myFunction);
function myFunction(e) {
// Código del manejador de eventos
}
La delegación de eventos permite añadir solo un listener para muchos elementos, lo que es más eficiente y más fácil de mantener.
11. Utiliza fetch
para Peticiones HTTP
Si todavía estás utilizando XMLHttpRequest
para tus peticiones AJAX, es hora de conocer fetch
.
La forma INCORRECTA:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
} else {
console.error('Error:', xhr.status);
}
}
};
xhr.send();
La forma CORRECTA:
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`Error HTTP: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error en la petición:', error);
});
// O con async/await
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`Error HTTP: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error en la petición:', error);
}
}
fetch
es más simple, más elegante y ya devuelve una Promise, integrándose perfectamente con async/await.
12. Entiende el "Hoisting"
Hoisting es un comportamiento en JavaScript donde las declaraciones son "elevadas" al principio de su scope.
La forma INCORRECTA:
console.log(x); // undefined (en lugar de error)
var x = 5;
// Las funciones declaradas con la palabra clave function son elevadas (hoisted) completamente
hello(); // ¡Esto funciona!
function hello() {
console.log("Hola");
}
// Pero las expresiones de función no lo son
bye(); // Error: bye is not a function
var bye = function () {
console.log("Adiós");
};
La forma CORRECTA:
// Declara siempre las variables al principio del scope
var x;
console.log(x); // undefined, pero al menos está claro
x = 5;
// Mejor aún, usa let y const que no tienen hoisting completo
// console.log(y); // Error: Cannot access 'y' before initialization
let y = 10;
// Para las funciones, mantener la consistencia
// O todas declaradas al principio, o todas como expresiones de función
function hello() {
console.log("Hola");
}
const bye = function () {
console.log("Adiós");
};
hello();
bye();
Comprender el hoisting evitará muchos bugs misteriosos en tu código.
13. Utiliza las DevTools para Debugging
Usar console.log
para debugging es como intentar reparar un reloj con un martillo. Funciona, pero no es la herramienta ideal.
La forma INCORRECTA:
function calculate() {
const a = getValueA();
console.log('a:', a);
const b = getValueB();
console.log('b:', b);
const result = a + b;
console.log('result:', result);
return result;
}
La forma CORRECTA:
function calculate() {
// Colocar un breakpoint aquí en las DevTools
const a = getValueA();
const b = getValueB();
const result = a + b;
// O usar la declaración debugger
debugger; // El navegador se detendrá aquí cuando las herramientas de desarrollador estén abiertas
return result;
}
Las herramientas de desarrollador modernas permiten:
- Inspeccionar valores en cualquier punto de la ejecución
- Ver la pila de llamadas
- Monitorizar expresiones
- Definir watchpoints para detectar cuándo cambia una propiedad
- Time-travel debugging (en algunos navegadores)
14. Utiliza los Operadores Spread y Rest
Los operadores spread (...
) y rest son herramientas poderosas que pueden simplificar bastante tu código.
La forma INCORRECTA:
// Copiar arrays
const original = [1, 2, 3];
const copy = [];
for (let i = 0; i < original.length; i++) {
copy.push(original[i]);
}
// Combinar objetos
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const combined = {};
Object.keys(obj1).forEach(key => {
combined[key] = obj1[key];
});
Object.keys(obj2).forEach(key => {
combined[key] = obj2[key];
});
// Funciones con número variable de argumentos
function sum() {
let result = 0;
for (let i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
}
La forma CORRECTA:
// Copiar arrays
const original = [1, 2, 3];
const copy = [...original];
// Combinar arrays
const array1 = [1, 2];
const array2 = [3, 4];
const combined = [...array1, ...array2]; // [1, 2, 3, 4]
// Combinar objetos
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const combined = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }
// Reemplazar propiedades específicas
const user = { name: 'João', age: 25 };
const updatedUser = { ...user, age: 26 }; // { name: 'João', age: 26 }
// Parámetro rest para funciones con número variable de argumentos
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
Estos operadores hacen el código mucho más limpio y expresivo, especialmente cuando se trabaja con arrays y objetos.
15. Aprende a Utilizar Map
, Set
, WeakMap
y WeakSet
Los tipos de colección JavaScript modernos ofrecen características poderosas que los objetos y arrays simples no poseen.
La forma INCORRECTA:
// Uso de objetos como diccionarios
const users = {};
users['joao@email.com'] = { name: 'João' };
users['maria@email.com'] = { name: 'Maria' };
// ¿El problema? Colisiones con las propiedades de Object.prototype
users['toString']; // ¡Ups, esto es una función!
// Verificar elementos únicos
function uniqueElements(array) {
const result = [];
for (let i = 0; i < array.length; i++) {
if (result.indexOf(array[i]) === -1) {
result.push(array[i]);
}
}
return result;
}
La forma CORRECTA:
// Map para diccionarios
const users = new Map();
users.set('joao@email.com', { name: 'João' });
users.set('maria@email.com', { name: 'Maria' });
console.log(users.get('joao@email.com')); // { name: 'João' }
console.log(users.has('pedro@email.com')); // false
// Set para colecciones de valores únicos
function uniqueElements(array) {
return [...new Set(array)];
}
console.log(uniqueElements([1, 2, 2, 3, 1, 4])); // [1, 2, 3, 4]
// WeakMap y WeakSet son útiles cuando es necesario asociar datos a objetos
// sin impedir que estos objetos sean recolectados por el garbage collector
const calculationsCache = new WeakMap();
function calculateExpensiveResult(obj) {
if (calculationsCache.has(obj)) {
return calculationsCache.get(obj);
}
// Simulando un cálculo costoso
const result = obj.a + obj.b;
calculationsCache.set(obj, result);
return result;
}
Estas estructuras ofrecen un mejor rendimiento para determinadas operaciones y evitan problemas comunes asociados a objetos y arrays regulares.
JavaScript es un lenguaje increíble y poderoso, pero como cualquier herramienta, necesita ser utilizada de la forma correcta para mostrar todo su potencial. Los consejos anteriores son solo la punta del iceberg, pero dominarlos ya te pondrá por delante de muchos programadores.
Recuerda: el código claro y legible es tan importante como el código que funciona. Como se suele decir por ahí, se escribe código una vez, pero se lee decenas de veces. Por eso, ¡hazte un favor a ti mismo (y a tus colegas) y adopta buenas prácticas!
Y si algún día te encuentras gritando al monitor sobre variables undefined, recuerda: no estás solo. Estamos todos juntos en este viaje de JavaScript, tropezando con promesas no resueltas y propiedades indefinidas, pero siempre aprendiendo algo nuevo con cada error.
¡Feliz codificación!