Ya sabes programar. El peligro está en suponer que JavaScript es Python con llaves. No lo es: los valores ausentes se multiplican, los arreglos son referencias, las promesas son valores comunes y el entorno de ejecución importa tanto como la sintaxis. Esta es la orientación que le daría a alguien de mi equipo que programa en Python antes de su primer pull request en JavaScript.
🎙️ Publicado y grabado: · Actualizado ·
La forma más rápida de escribir mal JavaScript es transliterar un archivo de Python. Traduce la intención, no la superficie. Un diccionario de Python suele convertirse en un objeto, pero un diccionario con claves arbitrarias puede necesitar un Map. Una comprensión de listas suele convertirse en map más filter, aunque muchas veces un bucle sencillo es más claro. JavaScript también tiene dos formas de ausencia: null y undefined. Trata undefined como “no se proporcionó o no se encontró” y reserva null para una ausencia deliberada.
# Python
names = [u["name"] for u in users if u.get("active")]
first = next((u for u in users if u["id"] == wanted_id), None)
// JavaScript: el signo de interrogación indica encadenamiento opcional
const names = users.filter(u => u.active).map(u => u.name);
const first = users.find(u => u.id === wantedId); // undefined si no existe
const city = first?.address?.city ?? "unknown";
===, no ==. El operador no estricto convierte los tipos antes de compararlos, de modo que 0 == false da true. Python te acostumbró a que comparar fuera aburrido. Mantén esa tranquilidad con la igualdad estricta.const primero; evita varMi regla es tajante: declara primero con const. Cámbialo a let solo cuando el nombre tenga que reasignarse. Evita var; su alcance de función y su elevación conservan errores históricos que no necesitas heredar. No confundas una declaración constante con un valor inmutable. Todavía puedes agregar elementos a un arreglo declarado con const, igual que un nombre en Python puede seguir apuntando a una lista modificada.
const user = { name: "Ada" };
user.name = "Grace"; // válido: se modificó el objeto
user = { name: "Lin" }; // TypeError: Assignment to constant variable.
let retries = 3;
retries -= 1; // es una reasignación, así que let es lo correcto
// No escribas código nuevo con var.
ReferenceError: Cannot access 'total' before initializationlet o const antes de declararlo. Mueve la declaración por encima de la primera lectura. No “corrijas” esto cambiándolo por var; solo convertirías un error evidente en un error con undefined.Quienes programan en Python tropiezan exactamente con dos valores: los arreglos y los objetos vacíos son verdaderos en JavaScript. Por eso, if (items) responde “¿esta variable contiene un arreglo?” y no “¿contiene algún elemento?”. Las cadenas se comportan como esperas. El cero, la cadena vacía, null, undefined, NaN y false son falsos. Todo lo demás es verdadero.
# Python
if not items:
print("empty")
// JavaScript
if (items.length === 0) console.log("empty");
if (Object.keys(options).length === 0) console.log("no options");
const shown = count ?? 10; // conserva un 0 válido
const wrong = count || 10; // sustituye 0 por 10
value or default de Python como value || default elimina ceros y cadenas vacías válidos. Usa ?? cuando solo null o undefined signifiquen que falta un valor.Los arreglos de JavaScript son referencias mutables, como las listas de Python, pero sus métodos se dividen en dos grupos. map, filter, slice y toSorted devuelven arreglos nuevos. push, pop, splice y sort modifican el original. El último produce errores excelentes porque el orden predeterminado es textual: diez aparece antes que dos.
# Python
prices = [10, 2, 30]
ordered = sorted(prices) # [2, 10, 30]
last = prices[-1]
// JavaScript
const prices = [10, 2, 30];
const ordered = prices.toSorted((a, b) => a - b);
const last = prices.at(-1);
const doubled = prices.map(n => n * 2);
TypeError: Cannot read properties of undefined (reading 'name')find() no encontró nada y la línea siguiente intentó acceder a una propiedad del resultado. Primero registra o inspecciona el valor justo antes del punto que falla. Después elige a propósito: valida con if (!user), termina la función antes, usa user?.name para datos realmente opcionales o lanza un error más claro. No repartas encadenamiento opcional por datos que son obligatorios.Un objeto es ideal para un registro con claves de texto conocidas: un usuario, una configuración o los datos de una API. Para claves arbitrarias, en especial claves que sean objetos, usa Map. Puedes acceder a las propiedades con puntos o corchetes. Los corchetes son obligatorios cuando la clave está en una variable. La desestructuración sustituye gran parte del desempaquetado repetitivo de diccionarios que escribías en Python, y el operador de propagación crea copias superficiales. Énfasis en superficiales: los objetos anidados siguen compartidos.
# Python
name = user["name"]
role = user.get("role", "reader")
updated = {**user, "active": True}
// JavaScript
const { name, role = "reader" } = user;
const updated = { ...user, active: true };
const field = "name";
console.log(user[field]); // clave en una variable
const byElement = new Map(); // claves arbitrarias
byElement.set(button, { clicks: 1 });
const copy = {...original}, cambiar copy.address.city también cambia original.address.city. Para datos similares a JSON que de verdad necesiten una copia profunda, usa structuredClone(original). Mejor aún: copia solo la rama anidada que pretendes actualizar.Las funciones son valores en ambos lenguajes, pero las API de JavaScript dependen muchísimo de los callbacks. Usa declaraciones para comportamientos importantes con nombre y funciones flecha para callbacks breves. JavaScript no tiene argumentos por palabra clave al estilo de Python, así que usa un objeto de opciones cuando los argumentos posicionales se vuelvan confusos. Desestructúralo en la lista de parámetros y define ahí los valores predeterminados.
# Python
def fetch_users(active=True, limit=20):
...
fetch_users(limit=5)
// JavaScript: un objeto de opciones sustituye los argumentos por palabra clave
function fetchUsers({ active = true, limit = 20 } = {}) {
return client.get("/users", { active, limit });
}
fetchUsers({ limit: 5 });
const labels = users.map(user => user.name.toUpperCase());
TypeError: formatUser is not a functionformatUser con console.log(typeof formatUser, formatUser). Si es undefined, comprueba los nombres de exportación e importación. Si es un objeto, probablemente importaste mal un espacio de nombres de módulo o una exportación predeterminada. Si escribiste por error onClick={formatUser()}, pasa la función: onClick={formatUser}.this casi siempreEl self de Python es explícito y estable. El this de JavaScript depende de cómo se llama una función, un hecho pésimo para tener presente durante una migración. Mi recomendación: evita this casi siempre. Prefiere funciones sencillas, cierres y datos pasados como argumentos. Aun así, necesitas entender this en clases y bibliotecas antiguas, pero no hace falta diseñar código de aplicación nuevo alrededor de él.
// Es frágil al pasarlo como callback: se pierde el receptor.
const counter = {
value: 0,
increment() { this.value += 1; }
};
setTimeout(counter.increment, 100);
// Sencillo y portable: el estado es explícito.
function increment(state) {
return { ...state, value: state.value + 1 };
}
let state = { value: 0 };
state = increment(state);
counter.increment.bind(counter) o envuelve la llamada: () => counter.increment(). No conviertas cada función en una función flecha al azar; entiende qué objeto debe ser dueño de la llamada.async/await: sintaxis familiar, entorno distintoEl bucle de eventos de JavaScript hace que una función asíncrona devuelva de inmediato una Promise. await pausa únicamente la función asíncrona actual, no el proceso. fetch tiene otra trampa: los errores HTTP, como un 404, no rechazan la promesa. Tienes que revisar response.ok. Usa Promise.all para tareas independientes; varios await consecutivos dentro de un bucle son correctos, pero innecesariamente lentos cuando las tareas no dependen entre sí.
async function fetchJson(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}
const [user, teams] = await Promise.all([
fetchJson("/api/users/42"),
fetchJson("/api/teams")
]);
Promise { <pending> }async y escribe const user = await loadUser(42). En el nivel superior de un módulo, los módulos ES modernos permiten await. En código CommonJS antiguo, usa una función asíncrona de entrada y llámala con un .catch(console.error) al final.await items.forEach(async item => save(item)) no espera a los callbacks. Usa await Promise.all(items.map(save)) para trabajar en paralelo, o un bucle for...of con await cuando el orden sea importante.Python tiene un modelo de importación dominante. JavaScript tiene el sistema moderno de módulos ES y el sistema CommonJS más antiguo de Node.js. El código nuevo debe usar módulos ES: export e import. En Node, declara esa elección con "type": "module" en package.json o usa la extensión .mjs. En el navegador, carga el script de entrada con type="module". Mezclar sistemas produce errores que parecen problemas de sintaxis, pero en realidad son problemas de configuración.
// math.js
export function mean(values) {
return values.reduce((sum, n) => sum + n, 0) / values.length;
}
export const version = "1.0";
// report.js — relative imports need the extension in browsers
import { mean, version } from "./math.js";
console.log(mean([2, 4, 9]), version);
SyntaxError: Unexpected token 'export'"type": "module" al package.json más cercano o cambia la extensión del archivo a .mjs. En HTML, usa <script type="module" src="app.js"></script>. Después conserva importaciones y exportaciones ESM de forma consistente; no lo tapes cambiando líneas al azar por require.Los rastros de pila de JavaScript son ruidosos, pero la rutina útil es la misma que en Python: lee el primer marco pertinente de tu código, inspecciona el valor en ese límite y reproduce el problema con la entrada más pequeña. La consola del navegador no sustituye la comprensión de los tipos. Pregunta por typeof, usa Array.isArray y comprueba si faltó esperar una promesa. Después corrige el lugar que produjo el valor incorrecto, no cada consumidor posterior.
// Keep this five-question checklist beside the debugger.
value === undefined // missing property, index, return?
value === null // deliberate empty value?
typeof value // "function", "object", "string"...
Array.isArray(value) // typeof [] is misleadingly "object"
value instanceof Promise // forgot await?
// JSON is stricter than a JS object literal:
JSON.parse("{'name': 'Ada'}");
SyntaxError: Unexpected token ' in JSON at position 1{"name":"Ada"}. Mejor aún: no construyas JSON a mano. Crea un objeto de JavaScript y llama a JSON.stringify(data); al leer una respuesta, comprueba el tipo de contenido antes de llamar a response.json().La postura práctica para migrar: usa const, igualdad estricta, comprobaciones explícitas de valores vacíos, funciones sencillas, módulos ES y await en cada promesa cuyo valor necesites. Evita var, evita this casi siempre y deja de intentar que JavaScript imite a Python. Cuando aceptas su modelo de ejecución, el lenguaje resulta mucho menos sorpresivo.