¿Qué pasa cuando se ejecuta este código?
let a = 1;
const b = 2;
a = 3;
b = 4;
console.log(a, b);let permite reasignación pero const no. Reasignar b lanza TypeError antes de que se ejecute console.log.
Cada pregunta de JavaScript con la respuesta correcta y una explicación clara.
let a = 1;
const b = 2;
a = 3;
b = 4;
console.log(a, b);let permite reasignación pero const no. Reasignar b lanza TypeError antes de que se ejecute console.log.
console.log(0 == "0");
console.log(0 === "0");La igualdad débil (==) convierte tipos, así que 0 == "0" es true. La igualdad estricta (===) no convierte, así que 0 === "0" es false.
const arr = [1, 2, 3];
arr[10] = 99;
console.log(arr.length);Asignar al índice 10 expande el array. La longitud pasa a 11; los índices 3–9 quedan dispersos (slots vacíos).
const s = "hello";
console.log(s[0], s.length);Los strings admiten acceso por índice (s[0] = 'h') y tienen propiedad length. "hello" tiene 5 caracteres.
const obj = { name: "Ada" };
const { name, age = 30 } = obj;
console.log(name, age);La destructuración con valor por defecto solo lo usa cuando la propiedad es undefined. age no está en obj, así que cae a 30.
console.log(typeof null);typeof null devuelve "object" — una vieja peculiaridad de JavaScript mantenida por compatibilidad.
console.log(NaN === NaN);
console.log(Number.isNaN(NaN));NaN es el único valor no igual a sí mismo. Usa Number.isNaN (u Object.is) para detectarlo.
console.log(null == undefined);
console.log(null === undefined);
console.log(null == 0);== considera null y undefined iguales entre sí, pero no a nada más (no convierte a 0).
const a = [1, 2, 3, 4, 5];
a.length = 2;
console.log(a);Asignar un valor menor a length trunca el array — los elementos extra se eliminan por completo.
const a = new Array(3);
const b = Array.of(3);
console.log(a.length, b.length);new Array(3) trata un único número como la longitud (3 slots vacíos). Array.of(3) lo trata como un único elemento.
let s = "hello";
s[0] = "H";
console.log(s);Los strings son inmutables. La asignación falla en silencio (en modo estricto lanza) — el string se queda "hello".
const x = 10;
console.log(`x is ${x > 5 ? "big" : "small"}`);Los template literals evalúan cualquier expresión dentro de ${...}, incluidos ternarios.
const o = { 2: "a", 1: "b", "x": "c" };
console.log(Object.keys(o));Las claves tipo entero van primero en orden ascendente, luego las claves string en orden de inserción.
function fn(...args) { return args.length; }
const arr = [1, 2, 3];
console.log(fn(...arr));Spread (...arr) en la llamada pasa los elementos como argumentos individuales; rest (...args) los recoge en la función.
const x = 5;
const label = x > 0 ? "+" : x < 0 ? "-" : "0";
console.log(label);Los ternarios anidados se evalúan de derecha a izquierda. x > 0 es true, así que gana la primera rama: "+".
const a = 0 || "fallback";
const b = "" || "fallback";
const c = "value" || "fallback";
console.log(a, b, c);|| devuelve el primer operando truthy o el último. 0 y "" son falsy, así que gana el fallback para a y b.
const a = 0 ?? "fallback";
const b = null ?? "fallback";
const c = undefined ?? "fallback";
console.log(a, b, c);?? solo cae al fallback con null o undefined — 0 se preserva (a diferencia de ||).
console.log(Boolean(""), Boolean("0"), Boolean([]), Boolean({}));Valores falsy: "", 0, NaN, null, undefined, false. Strings no vacíos Y cualquier objeto (incluidos [] y {}) son truthy.
console.log(1 + "2" + 3);
console.log(1 + 2 + "3");+ es asociativo por la izquierda. En cuanto aparece un operando string, el resto concatena. "1"+"2"+3 → "123"; 1+2 y luego +"3" → "33".
console.log(parseInt("08"));
console.log(parseInt("08", 10));El parseInt moderno usa por defecto base 10 para strings no-0x (el viejo octal por defecto con 0 inicial se quitó en ES5+). Aun así pasa siempre la base por claridad.
const a = Array.from({ length: 3 }, (_, i) => i * 2);
console.log(a);Array.from con un objeto solo-length más un mapper es la forma canónica de construir un array de N valores calculados.
const a = [1, NaN, 3];
console.log(a.indexOf(NaN), a.includes(NaN));indexOf usa === (NaN nunca coincide consigo mismo). includes usa SameValueZero, que considera NaN igual a NaN.
const name = "Ada", age = 36;
const user = { name, age, role: "admin" };
console.log(user.name, user.role);El atajo de propiedad { name } equivale a { name: name } — usa el valor de la variable.
const o = { x: 1 };
o.x = 2;
o.y = 3;
console.log(o.x, o.y);const protege la vinculación (no puedes reasignar o), pero el objeto en sí sigue siendo mutable.
const arr = ["a", "b", "c"];
arr.extra = "x";
const ofs = []; for (const v of arr) ofs.push(v);
const ins = []; for (const k in arr) ins.push(k);
console.log(ofs.length, ins.length);for...of itera los valores indexados del array (3). for...in recorre todas las claves string enumerables, incluida la añadida "extra" (4).
function makeCounter() {
let n = 0;
return () => ++n;
}
const c = makeCounter();
c(); c();
console.log(c());El closure captura n. Cada llamada lo incrementa; la tercera invocación devuelve 3.
console.log(typeof a);
var a = 1;
console.log(typeof b);Las declaraciones var se hoistean (inicializadas a undefined). typeof sobre un nombre no declarado b devuelve 'undefined' sin lanzar.
const obj = {
name: "Ada",
greet() { return `hi ${this.name}`; }
};
const g = obj.greet;
console.log(g());Los métodos desacoplados pierden su this. En un módulo / contexto estricto this es undefined y acceder a .name lanza; en globals no-strict del navegador es window, donde window.name vale '' pero suele leerse como undefined para nuestro caso — la mayoría de entornos modernos dan 'hi undefined'.
const a = [1, 2];
const b = [3, 4];
const c = [...a, ...b, 5];
console.log(c.length, c[2]);Spread aplana ambos arrays en c y luego añade 5: [1, 2, 3, 4, 5]. Longitud 5, índice 2 es 3.
function f(a, b = 2, ...rest) {
return [a, b, rest];
}
console.log(f(1, undefined, 3, 4));Los valores por defecto se activan con undefined, así que b pasa a 2. El rest parameter recoge los argumentos restantes en un array.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}var es de ámbito de función — todos los callbacks cierran sobre la misma i, que ya es 3 cuando corren.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}let crea una nueva vinculación por iteración, así que cada callback cierra sobre su propio i.
const obj = {
name: "Ada",
greet: () => `hi ${this?.name}`,
};
console.log(obj.greet());Las funciones flecha no enlazan su propio this — queda el del scope envolvente (aquí, top-level → undefined o globalThis).
class Counter {
n = 0;
inc() { this.n++; }
}
const c = new Counter();
const f = c.inc;
f();
console.log(c.n);Los cuerpos de clase están en modo estricto por defecto. El f() desacoplado tiene this === undefined, así que this.n++ lanza TypeError.
const a = { x: { y: 1 } };
const b = { ...a };
b.x.y = 99;
console.log(a.x.y);Spread hace una copia superficial — los objetos anidados se comparten por referencia. Usa structuredClone para copia profunda.
console.log(typeof foo);
console.log(typeof bar);
function foo() {}
var bar = function() {};Las declaraciones de función se hoistean con su cuerpo. Las function expressions asignadas a var solo hoistean la vinculación var (inicialmente undefined).
const x = (function() {
return 42;
})();
console.log(x);Una IIFE — definida e invocada inmediatamente. El () final llama a la función, así que x recibe el valor de retorno.
function add(a, b) { return a + b; }
const add5 = add.bind(null, 5);
console.log(add5(3));bind aplica parcialmente argumentos tras this. add5 tiene a fijado en 5, así que add5(3) calcula 5 + 3.
const sum = [1, 2, 3, 4]
.filter(n => n % 2 === 0)
.map(n => n * 10)
.reduce((acc, n) => acc + n, 0);
console.log(sum);filter → [2,4]; map ×10 → [20,40]; reduce + → 60.
const o = {
_x: 1,
get x() { return this._x; },
set x(v) { this._x = v * 2; },
};
o.x = 5;
console.log(o.x);El setter duplica el valor antes de guardarlo (this._x = 10). El getter luego lo devuelve tal cual.
const data = { user: { profile: { age: 25 } } };
const { user: { profile: { age } } } = data;
console.log(age);La destructuración anidada sigue la estructura del objeto — solo age queda ligada como variable.
let calls = 0;
function id() { calls++; return calls; }
function f(x = id()) { return x; }
f(); f(10); f();
console.log(calls);Las expresiones de parámetro por defecto solo se evalúan si el arg es undefined. f(10) salta el default, así que id() solo corre dos veces.
console.log([10, 1, 2].sort());El sort por defecto es lexicográfico — los elementos se comparan como strings. Usa .sort((a,b) => a-b) para orden numérico.
const a = { x: 1, y: 2 };
const b = { ...a, x: 99 };
console.log(b);Las propiedades listadas después sobrescriben las anteriores. El orden importa: { x: 99, ...a } dejaría ganar a a.x.
const u = { name: "Ada", contact: null };
const email = u?.contact?.email ?? "n/a";
console.log(email);?. corta en null/undefined devolviendo undefined; ?? luego cae a "n/a".
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]());let crea una nueva vinculación por iteración, así que cada closure captura su propio i.
const o = {
vals: [1, 2, 3],
sum() {
let total = 0;
this.vals.forEach(function(v) { total += v; });
return total;
}
};
console.log(o.sum());total se captura vía closure (no via this), así que el this de la función interna no importa — la suma es 6.
function head(first, ...rest) { return [first, rest.length]; }
console.log(head("a", "b", "c", "d"));rest recoge todo lo posterior a first en un array de 3.
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b, a.description === b.description);Cada llamada a Symbol() produce un valor único. La description opcional es solo una etiqueta y no afecta la igualdad.
const m = new Map();
m.set(2, "a"); m.set(1, "b"); m.set("x", "c");
console.log([...m.keys()]);Map preserva el orden de inserción para cualquier tipo de clave — al contrario que los objetos planos, donde las claves tipo entero se reordenan ascendentemente.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");Primero el código síncrono (1, 4). Luego microtasks (Promise.then → 3). Después macrotasks (setTimeout → 2).
function* gen() {
yield 1;
yield 2;
return 3;
}
const g = gen();
console.log(g.next().value, g.next().value, g.next().value);Cada next() devuelve { value, done }. La tercera llamada alcanza el return, exponiendo el 3 retornado con done=true.
class A {
greet() { return "A"; }
}
class B extends A {
greet() { return "B" + super.greet(); }
}
console.log(new B().greet());B.greet devuelve 'B' concatenado con super.greet() (la implementación de A = 'A'), dando 'BA'.
const target = { x: 1 };
const handler = {
get(obj, prop) { return prop in obj ? obj[prop] : 0; }
};
const p = new Proxy(target, handler);
console.log(p.x, p.y);El trap get del Proxy devuelve el valor real si la prop existe, si no 0. p.x → 1, p.y → 0 (y no está en target).
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));Los objetos planos heredan de Object.prototype. Object.prototype mismo está en la cima de la cadena, así que su prototipo es null.
function* g() {
yield 1;
yield 2;
return 3;
}
const it = g();
console.log(it.next().value, it.next().value, it.next().value, it.next().value);yield emite valores; return es el valor final (con done=true). Después, .next() da { value: undefined, done: true }.
const target = { x: 1 };
const proxy = new Proxy(target, {
get(t, p) { return p in t ? t[p] : "default"; }
});
console.log(proxy.x, proxy.y);El trap get intercepta lecturas de propiedad. Las propiedades existentes pasan; las que faltan devuelven "default".
const range = {
from: 1, to: 3,
[Symbol.iterator]() {
let cur = this.from, end = this.to;
return {
next: () => cur <= end ? { value: cur++, done: false } : { value: undefined, done: true }
};
}
};
console.log([...range]);Implementar [Symbol.iterator] hace al objeto iterable — spread lo invoca y recoge valores hasta que done sea true.
const o = { x: 1 };
console.log(Reflect.get(o, "x"), Reflect.has(o, "y"));Reflect refleja operadores — Reflect.get es como obj[p]; Reflect.has como "p" in obj.
const wm = new WeakMap();
let key = {};
wm.set(key, "hello");
console.log(wm.get(key));
key = null;
console.log(wm.has({}));Las claves de WeakMap deben ser objetos comparados por referencia. {} es un objeto nuevo — no la clave original — así que .has devuelve false.
console.log("A");
Promise.resolve().then(() => console.log("B"));
Promise.resolve().then(() => console.log("C"));
console.log("D");Primero el código síncrono (A, D), luego microtasks en orden FIFO (B, C).
console.log("1");
queueMicrotask(() => console.log("2"));
Promise.resolve().then(() => console.log("3"));
console.log("4");Tanto queueMicrotask como Promise.then planifican microtasks. Corren después del código síncrono, en orden de planificación.
class Box {
#value;
constructor(v) { this.#value = v; }
get() { return this.#value; }
}
const b = new Box(42);
console.log(b.get(), b["#value"]);Los campos privados (#) solo son accesibles dentro del cuerpo de clase; b["#value"] lee una propiedad string normal que no existe.
class A {
constructor() { this.tag = "A"; }
}
class B extends A {
constructor() {
super();
this.tag = "B";
}
}
console.log(new B().tag);super() ejecuta el constructor de A (pone this.tag = "A"); la siguiente asignación lo sobrescribe a "B".
class Animal {
static [Symbol.hasInstance]() { return true; }
}
console.log({} instanceof Animal);instanceof delega en Symbol.hasInstance — una implementación personalizada puede devolver lo que quiera.
const o = Object.create(null);
console.log(Object.getPrototypeOf(o));
console.log(o.toString);Object.create(null) crea un objeto sin prototipo — no hay Object.prototype.toString que heredar.
function show() { return [this.tag, ...arguments]; }
console.log(show.call({ tag: "A" }, 1, 2));
console.log(show.apply({ tag: "B" }, [3, 4]));call recibe los args como lista; apply como array. Ambos fijan this al primer argumento.
const proto = {
get hi() { return `hi ${this.name}`; }
};
const o = Object.create(proto);
o.name = "Ada";
console.log(o.hi);Los getters definidos en el prototipo corren con this igual al receptor — o, que tiene name = "Ada".
const o = {};
Object.defineProperty(o, "x", { value: 1, configurable: false });
try {
Object.defineProperty(o, "x", { value: 2 });
console.log(o.x);
} catch (e) {
console.log("error");
}Un descriptor de datos no configurable aún permite cambiar value (y pasar writable de true a false). Otros atributos quedan bloqueados.
const o = { a: 1 };
Object.defineProperty(o, "b", { value: 2, enumerable: false });
console.log(Object.keys(o), Object.getOwnPropertyNames(o));Object.keys devuelve solo propiedades propias enumerables. getOwnPropertyNames devuelve todas las propiedades propias con clave string.
function makeIter(arr) {
let i = 0;
return {
next: () => i < arr.length
? { value: arr[i++], done: false }
: { value: undefined, done: true },
[Symbol.iterator]() { return this; },
};
}
console.log([...makeIter(["a","b"])]);Un iterador que se devuelve a sí mismo desde [Symbol.iterator] también es iterable, así que spread puede consumirlo.
async function* nums() {
yield 1; yield 2;
}
const out = [];
for await (const n of nums()) out.push(n);
console.log(out);for-await-of espera cada valor yielded, así que out recoge los números ya desempaquetados.
const safe = new Proxy({ x: 1 }, {
has(t, p) { return p === "x"; }
});
console.log("x" in safe, "y" in safe);El trap has intercepta el operador in. Solo permitimos "x".
const o = Object.freeze({ x: 1, nested: { y: 2 } });
o.x = 99;
o.nested.y = 99;
console.log(o.x, o.nested.y);Object.freeze es superficial — la x de nivel superior está bloqueada, pero el objeto anidado sigue siendo mutable.
class A {}
A.prototype.constructor = function() { return { tag: "fake" }; };
console.log(new A().tag);new A() llama a A directamente (el constructor de clase original), no a la propiedad constructor del prototipo.
async function f() { return 42; }
const r = f();
console.log(r);Llamar a una función async devuelve una Promise. Como la función retorna de forma síncrona, la Promise ya está fulfilled con 42 (se imprime Promise { 42 }).
Promise.all([
Promise.resolve(1),
Promise.reject("err"),
Promise.resolve(3),
]).then((v) => console.log("ok", v))
.catch((e) => console.log("fail", e));Promise.all rechaza al primer rejection — el catch se dispara con esa razón y los valores fulfilled se descartan.
async function main() {
console.log("a");
await Promise.resolve();
console.log("b");
}
main();
console.log("c");Primero el código síncrono de main ('a'), luego await cede el control. Se imprime 'c', luego la microtask retoma main e imprime 'b'.
Promise.allSettled([
Promise.resolve(1),
Promise.reject("x"),
]).then((arr) => console.log(arr.map(r => r.status)));Promise.allSettled resuelve con un array de { status, value | reason }. Status es 'fulfilled' o 'rejected'.
Tras cada macrotask, el motor vacía la cola de microtasks antes del siguiente macrotask o del render. setTimeout(0) es un macrotask y espera al menos un ciclo.
const a = new Promise(r => setTimeout(() => r("a"), 50));
const b = new Promise(r => setTimeout(() => r("b"), 10));
const winner = await Promise.race([a, b]);
console.log(winner);Promise.race resuelve con la entrada que se asiente primero — b se asienta tras 10ms, a tras 50ms.
async function f() { return 5; }
const r = f();
console.log(r instanceof Promise, await r);Una función async siempre devuelve una Promise — aunque el cuerpo devuelva un valor plano, se envuelve.
async function f() {
try {
await Promise.reject(new Error("boom"));
} catch (e) {
return "caught:" + e.message;
}
}
console.log(await f());await sobre una promise rechazada lanza de forma síncrona dentro de la función async — try/catch la maneja.
async function f() {
throw new Error("x");
}
try {
f();
console.log("after call");
} catch (e) {
console.log("caught");
}Una función async que retorna un rejection NO es un throw síncrono — sin await, el error pasa a ser unhandled rejection.
try {
await Promise.all([
Promise.resolve(1),
Promise.reject(new Error("nope")),
Promise.resolve(3),
]);
} catch (e) {
console.log("caught:", e.message);
}Promise.all rechaza en cuanto cualquier entrada rechaza — el rejection corta la espera.
try {
await Promise.any([
Promise.reject("a"),
Promise.reject("b"),
]);
} catch (e) {
console.log(e.constructor.name, e.errors);
}Promise.any rechaza con un AggregateError con todas las razones solo cuando todas las entradas rechazan.
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync");Primero el código síncrono, luego microtasks (Promise.then), luego macrotasks (setTimeout).
const r = await Promise.resolve(1)
.then(v => v + 1)
.then(v => v * 10);
console.log(r);Cada .then ve el valor de retorno anterior: 1 → 2 → 20.
async function getX() { return 5; }
async function run() {
const x = getX();
console.log(typeof x.then, x + 1);
}
await run();Olvidar await deja x como Promise. x + 1 lo convierte vía toString() → "[object Promise]1".
async function run() {
const items = [1, 2, 3];
const out = [];
for (const n of items) {
out.push(await Promise.resolve(n * 2));
}
return out;
}
console.log(await run());await dentro de for…of corre secuencialmente. Cada iteración espera a la promise previa — out recibe los valores desempaquetados.
async function run() {
const items = [1, 2, 3];
return Promise.all(items.map(async n => n * 2));
}
console.log(await run());Map+async corre las funciones async en paralelo, cada una devuelve una Promise. Promise.all las desempaqueta todas.
const out = await Promise.allSettled([
Promise.resolve(1),
Promise.reject("x"),
]);
console.log(out.map(r => r.status));Promise.allSettled nunca rechaza — devuelve objetos con forma { status: 'fulfilled' | 'rejected', value | reason }.
function delay(ms) {
return new Promise(r => setTimeout(r, ms));
}
const start = Date.now();
await Promise.all([delay(50), delay(50), delay(50)]);
const elapsed = Date.now() - start;
console.log(elapsed >= 50 && elapsed < 200);Los tres delays corren concurrentemente, así que el tiempo total es aproximadamente el más largo (~50ms), no 150ms.
try {
await Promise.resolve(1).then(v => { throw new Error("oops"); });
} catch (e) {
console.log("caught:" + e.message);
}Lanzar dentro de un handler .then convierte la cadena en un rejection, que await vuelve a convertir en throw síncrono.
async function* gen() { yield 1; yield 2; yield 3; }
const out = [];
for await (const n of gen()) out.push(n);
console.log(out);Un async generator hace yield de valores envueltos en promise; for-await-of espera cada uno.
console.log("before");
const p = new Promise((res) => {
console.log("executor");
res();
});
console.log("after");
await p;
console.log("done");El executor de la Promise corre de forma síncrona al construir la Promise.
const thenable = { then(res) { res("hi"); } };
const out = await thenable;
console.log(out);await acepta cualquier thenable. El motor llama a .then(resolve, reject) y usa el valor resuelto.
// Imagine: window.addEventListener('unhandledrejection', e => console.log('unhandled'));
Promise.reject(new Error("boom"));Un rejection sin .catch / await dispara el evento unhandledrejection en window (o process en Node).
function defer() {
let resolve;
const promise = new Promise(r => { resolve = r; });
return { promise, resolve };
}
const d = defer();
setTimeout(() => d.resolve(42), 10);
console.log(await d.promise);Capturar el resolver permite settle una Promise desde fuera del executor — el clásico patrón deferred.
let a;
queueMicrotask(() => a = "qm");
Promise.resolve().then(() => a = "p");
await Promise.resolve();
console.log(a);Ambos planifican microtasks. Corren en orden FIFO: queueMicrotask pone primero "qm", luego el callback .then sobrescribe con "p".
const user = { name: "Ada", profile: null };
console.log(user?.profile?.email ?? "n/a");Optional chaining corta en profile (null) dando undefined. ?? cae a 'n/a' porque el lado izquierdo es nullish.
console.log(0 ?? "fallback");
console.log("" ?? "fallback");
console.log(null ?? "fallback");?? cae solo con null o undefined. 0 y "" se conservan tal cual (a diferencia de ||, que los reemplazaría).
const fn = null;
console.log(fn?.());La llamada opcional (?.()) corta a undefined cuando el callee es null/undefined en vez de lanzar TypeError.
const entries = [["a", 1], ["b", 2]];
const obj = Object.fromEntries(entries);
console.log(obj.a, obj.b);Object.fromEntries invierte Object.entries — construye un objeto a partir de pares clave/valor.
const a = { x: { y: 1 } };
const b = structuredClone(a);
b.x.y = 99;
console.log(a.x.y);structuredClone hace una copia profunda. Mutar b.x.y no afecta a a, así que a.x.y sigue siendo 1. Disponible en Node y navegadores modernos sin polyfills.
const obj = { greet: null };
console.log(obj.greet?.());
console.log(obj.missing?.());?.() corta a undefined cuando el valor previo es null o undefined — no lanza error.
const arr = [1];
const [a, b = 99, c = 100] = arr;
console.log(a, b, c);Los defaults en destructuración de array se activan con undefined — tanto b como c toman sus defaults.
const m = new Map([["a", 1], ["b", 2]]);
const o = Object.fromEntries(m);
console.log(o.a, o.b);Object.fromEntries acepta cualquier iterable de pares [clave, valor] — incluidos Maps.
let a = "";
let b = "value";
a ||= "fallback";
b ||= "fallback";
console.log(a, b);||= asigna solo cuando el lado izquierdo es falsy. "" es falsy → a pasa a "fallback"; "value" es truthy → b no cambia.
let a = 0;
let b = null;
a ??= 99;
b ??= 99;
console.log(a, b);??= asigna solo cuando el lado izquierdo es null o undefined. 0 se preserva; null se reemplaza.
const big = 1_000_000;
console.log(big === 1000000);Los separadores numéricos (_) son azúcar sintáctico — se eliminan al parsear y no afectan al valor.
const a = 9007199254740993n;
const b = a + 1n;
console.log(b);BigInt maneja enteros de tamaño arbitrario con exactitud. La 'n' final marca un literal BigInt.
const arr = [10, 20, 30];
console.log(arr.at(-1), arr.at(-2));Array.prototype.at admite índices negativos, contando desde el final.
console.log("a-b-c".replaceAll("-", "/"));String.prototype.replaceAll reemplaza cada coincidencia sin necesitar un regex global.
const o = { x: 1 };
console.log(Object.hasOwn(o, "x"), Object.hasOwn(o, "toString"));Object.hasOwn comprueba solo propiedades propias — "toString" se hereda de Object.prototype.
console.log([1, [2, [3, [4]]]].flat(2));flat(depth) aplana hasta depth niveles. flat(2) deja intacto el [4] más profundo. Usa flat(Infinity) para aplanar todo.
console.log([1, 2, 3].flatMap(n => [n, n * 10]));flatMap es map seguido de flat(1) — útil para transformaciones uno-a-muchos.
const winner = await Promise.any([
Promise.reject("a"),
Promise.resolve("b"),
Promise.resolve("c"),
]);
console.log(winner);Promise.any resuelve con el primer valor fulfilled, ignorando los rejections.
globalThis.__token = 42;
console.log(globalThis.__token);globalThis es el handle estandarizado al objeto global — funciona en navegador, Node, workers y Deno por igual.
// In an ES module:
// const data = await fetch("/api").then(r => r.json());
console.log("module loaded after data");Top-level await suspende la evaluación del módulo. Los módulos que importan esperan antes de ejecutar su propio cuerpo.
const items = [1, 2, 3, 4];
const grouped = Object.groupBy(items, n => n % 2 === 0 ? "even" : "odd");
console.log(grouped.even, grouped.odd);Object.groupBy (ES2024) agrupa elementos por el valor devuelto por el callback, devolviendo un objeto plano indexado por grupo.
let target = { name: "Ada" };
const ref = new WeakRef(target);
console.log(ref.deref()?.name);WeakRef.deref() devuelve el target mientras siga alcanzable. Una vez que el GC lo recoge, deref() devuelve undefined.
const d = new Date(2026, 0, 15);
const c = structuredClone({ when: d });
console.log(c.when instanceof Date, c.when.getTime() === d.getTime());structuredClone preserva tipos integrados como Date, Map, Set, RegExp, ArrayBuffer — no solo objetos planos.
console.log([1, 2, 3, 4].findLast(n => n < 3));findLast recorre de derecha a izquierda y devuelve el último elemento que coincide — aquí 2 (el último valor < 3).
function sum(a, b, c) { return a + b + c; }
const args = [1, 2, 3];
console.log(sum(...args));Spread en la llamada expande el iterable en argumentos individuales — a=1, b=2, c=3.