CAP 11 · LEC 04·Estructuras de datos modernas

Symbol: valores únicos e irremplazables

Symbol es el séptimo tipo primitivo de JavaScript: cada Symbol creado es garantizadamente único, incluso si tiene la misma descripción. Son la herramienta ideal para propiedades de objeto que no deben colisionar con nada, y los well-known symbols permiten personalizar el comportamiento del lenguaje.

● AVANZADO8 min lectura3 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

¿Qué es un Symbol? — único, primitivo, no colisionable

Un Symbol es un valor primitivo. Cada llamada a Symbol() produce un valor distinto, garantizado. Dos symbols con la misma descripción son siempre diferentes.

// Cada Symbol es único — siempre const s1 = Symbol("id"); const s2 = Symbol("id"); console.log(s1 === s2); // false — son diferentes aunque la descripción sea igual console.log(typeof s1); // "symbol" console.log(s1.toString()); // "Symbol(id)" console.log(s1.description); // "id" (ES2019+) // Los symbols NO se convierten implícitamente a string // console.log("Prefijo: " + s1); // ❌ TypeError // console.log(`Valor: ${s1}`); // ❌ TypeError en strict mode // Sí puedes convertirlos explícitamente console.log(String(s1)); // "Symbol(id)" console.log(s1.toString()); // "Symbol(id)" // Symbol.for() — registro global compartido (sí puede reutilizar) const global1 = Symbol.for("app.config"); const global2 = Symbol.for("app.config"); console.log(global1 === global2); // true — mismo símbolo del registro global console.log(Symbol.keyFor(global1)); // "app.config" // Sin descripción — sigue siendo único const anonimo = Symbol(); console.log(typeof anonimo); // "symbol"
// TypeScript tiene tipo literal para symbols únicos: unique symbol const ID = Symbol("id"); type ID = typeof ID; // unique symbol // unique symbol — solo asignable a const, tipo estricto const CLAVE_A: unique symbol = Symbol("a"); const CLAVE_B: unique symbol = Symbol("b"); // CLAVE_A y CLAVE_B son tipos distintos — no intercambiables // function usarA(s: typeof CLAVE_A): void {} // usarA(CLAVE_B); // ❌ Error de tipo // symbol (minúsculas) — cualquier symbol, menos restrictivo let simboloDinamico: symbol = Symbol("dinamico"); simboloDinamico = Symbol("otro"); // ✅ // Symbol.for — devuelve symbol (no unique symbol) const global: symbol = Symbol.for("app.config"); console.log(Symbol.keyFor(global)); // "app.config" // En TypeScript moderno puedes usar 'const' assertions con symbols const ESTADO = { ACTIVO: Symbol("activo"), INACTIVO: Symbol("inactivo"), PENDIENTE: Symbol("pendiente"), } as const; type Estado = typeof ESTADO[keyof typeof ESTADO];
Salidafalse symbol Symbol(id) id Symbol(id) true app.config

Crear y usar Symbols — descripción y Symbol.for

// Symbols como constantes de estado — sin riesgo de colisión de strings const Estado = { PENDIENTE: Symbol("pendiente"), ACTIVO: Symbol("activo"), CANCELADO: Symbol("cancelado"), }; function procesarPedido(pedido) { switch (pedido.estado) { case Estado.PENDIENTE: return "Procesando..."; case Estado.ACTIVO: return "En camino"; case Estado.CANCELADO: return "Cancelado"; default: throw new Error("Estado desconocido"); } } const pedido = { id: 42, estado: Estado.ACTIVO }; console.log(procesarPedido(pedido)); // "En camino" // A diferencia de strings, nadie puede crear accidentalmente Estado.ACTIVO // si alguien escribe "activo" (string), NO iguala a Symbol("activo") const estadoIncorrecto = "activo"; console.log(estadoIncorrecto === Estado.ACTIVO); // false — protegido por diseño // Symbol.for — útil cuando dos módulos distintos necesitan el mismo símbolo // modulo-a.js: const EVENTO_LISTO = Symbol.for("mi-app.listo"); // modulo-b.js (separado): const MISMO_EVENTO = Symbol.for("mi-app.listo"); console.log(EVENTO_LISTO === MISMO_EVENTO); // true
// Enum-like con symbols — más seguro que string enums const Estado = Object.freeze({ PENDIENTE: Symbol("pendiente"), ACTIVO: Symbol("activo"), CANCELADO: Symbol("cancelado"), } as const); type Estado = typeof Estado[keyof typeof Estado]; interface Pedido { id: number; estado: Estado; } function procesarPedido(pedido: Pedido): string { switch (pedido.estado) { case Estado.PENDIENTE: return "Procesando..."; case Estado.ACTIVO: return "En camino"; case Estado.CANCELADO: return "Cancelado"; default: // TypeScript detecta casos no manejados en el switch const _exhaustivo: never = pedido.estado; throw new Error("Estado desconocido"); } } const pedido: Pedido = { id: 42, estado: Estado.ACTIVO }; console.log(procesarPedido(pedido)); // "En camino" // Nadie puede asignar un estado incorrecto // const invalido: Pedido = { id: 1, estado: Symbol("activo") }; // ❌ Error de tipo
SalidaEn camino false true

Symbols como claves de objeto — propiedades no enumerables

Las propiedades Symbol en un objeto son invisibles para la mayoría de las operaciones de enumeración: for...in, Object.keys(), JSON.stringify() las ignoran.

const METADATA = Symbol("metadata"); const VALIDADO = Symbol("validado"); const usuario = { nombre: "Ana", email: "ana@ejemplo.com", [METADATA]: { creadoEn: new Date(), version: 1 }, [VALIDADO]: true, }; // Las propiedades Symbol NO aparecen en enumeración normal console.log(Object.keys(usuario)); // ["nombre", "email"] — sin símbolos console.log(JSON.stringify(usuario)); // {"nombre":"Ana","email":"ana@ejemplo.com"} for (const clave in usuario) { console.log(clave); // "nombre", "email" — no los símbolos } // Para acceder a ellas debes tener el símbolo console.log(usuario[METADATA]); // { creadoEn: ..., version: 1 } console.log(usuario[VALIDADO]); // true // Para listar claves Symbol explícitamente console.log(Object.getOwnPropertySymbols(usuario)); // [Symbol(metadata), Symbol(validado)] // Reflect.ownKeys — devuelve TODAS las claves, incluyendo symbols console.log(Reflect.ownKeys(usuario)); // ["nombre", "email", Symbol(metadata), Symbol(validado)]
const METADATA: unique symbol = Symbol("metadata"); const VALIDADO: unique symbol = Symbol("validado"); interface MetaDatos { creadoEn: Date; version: number; } // TypeScript permite anotar propiedades symbol con computed property types interface UsuarioConMeta { nombre: string; email: string; [METADATA]: MetaDatos; [VALIDADO]: boolean; } const usuario: UsuarioConMeta = { nombre: "Ana", email: "ana@ejemplo.com", [METADATA]: { creadoEn: new Date(), version: 1 }, [VALIDADO]: true, }; // Acceso tipado const meta: MetaDatos = usuario[METADATA]; console.log(meta.version); // 1 const validado: boolean = usuario[VALIDADO]; console.log(validado); // true // JSON.stringify los omite — ideal para metadata interna const json = JSON.stringify(usuario); console.log(json); // {"nombre":"Ana","email":"ana@ejemplo.com"}
Salida["nombre", "email"] {"nombre":"Ana","email":"ana@ejemplo.com"} nombre email { creadoEn: ..., version: 1 } true

Well-known symbols — personalizar el comportamiento del lenguaje

JavaScript tiene un conjunto de symbols predefinidos en Symbol.* que permiten sobrescribir comportamientos internos del lenguaje.

// Symbol.iterator — hace un objeto iterable con for...of class Rango { constructor(inicio, fin) { this.inicio = inicio; this.fin = fin; } [Symbol.iterator]() { let actual = this.inicio; const fin = this.fin; return { next() { return actual <= fin ? { value: actual++, done: false } : { value: undefined, done: true }; }, }; } } console.log([...new Rango(1, 5)]); // [1, 2, 3, 4, 5] // Symbol.toPrimitive — controla la conversión a primitivo class Moneda { constructor(cantidad, divisa = "MXN") { this.cantidad = cantidad; this.divisa = divisa; } [Symbol.toPrimitive](hint) { if (hint === "number") return this.cantidad; if (hint === "string") return `${this.cantidad} ${this.divisa}`; return this.cantidad; // "default" } } const precio = new Moneda(150); console.log(+precio); // 150 (hint: "number") console.log(`Precio: ${precio}`); // "Precio: 150 MXN" (hint: "string") console.log(precio + 0); // 150 (hint: "default") // Symbol.hasInstance — personaliza instanceof class ColeccionPar { static [Symbol.hasInstance](instancia) { return Array.isArray(instancia) && instancia.length % 2 === 0; } } console.log([1, 2, 3, 4] instanceof ColeccionPar); // true console.log([1, 2, 3] instanceof ColeccionPar); // false
// Symbol.iterator tipado class Rango implements Iterable<number> { constructor( private readonly inicio: number, private readonly fin: number, ) {} [Symbol.iterator](): Iterator<number> { let actual = this.inicio; const fin = this.fin; return { next(): IteratorResult<number> { return actual <= fin ? { value: actual++, done: false } : { value: undefined as unknown as number, done: true }; }, }; } } console.log([...new Rango(1, 5)]); // [1, 2, 3, 4, 5] // Symbol.toPrimitive tipado class Moneda { constructor( private readonly cantidad: number, private readonly divisa: string = "MXN" ) {} [Symbol.toPrimitive](hint: "number" | "string" | "default"): number | string { if (hint === "number") return this.cantidad; if (hint === "string") return `${this.cantidad} ${this.divisa}`; return this.cantidad; } } const precio = new Moneda(150); console.log(`Precio: ${precio}`); // "Precio: 150 MXN" console.log(+precio); // 150
Salida[1, 2, 3, 4, 5] 150 Precio: 150 MXN 150 true false

Practica