Symbol es un tipo primitivo de JavaScript introducido en ES6. Cada valor Symbol es único e irrepetible, incluso si dos sÃmbolos se crean con la misma descripción. Su principal uso es crear claves de propiedad que no colisionen con otras propiedades, sean del código de usuario o de la plataforma.
Crear y usar Symbol
Un sÃmbolo se crea llamando a Symbol(descripción). La descripción es opcional y solo sirve para depuración. Dos llamadas con la misma descripción producen sÃmbolos distintos:
const s1 = Symbol('id');
const s2 = Symbol('id');
console.log(s1 === s2); // false — siempre únicos
console.log(typeof s1); // 'symbol'
console.log(s1.toString()); // 'Symbol(id)'
console.log(s1.description); // 'id'
// Uso como clave de propiedad
const ID = Symbol('id');
const SECRETO = Symbol('secreto');
const usuario = {
nombre: 'Ana',
[ID]: 42, // clave simbólica
[SECRETO]: 'token_privado'
};
console.log(usuario[ID]); // 42
console.log(usuario.nombre); // 'Ana'
// Las claves simbólicas no aparecen en enumeración normal
console.log(Object.keys(usuario)); // ['nombre']
console.log(JSON.stringify(usuario)); // {"nombre":"Ana"}
console.log(Object.getOwnPropertySymbols(usuario)); // [Symbol(id), Symbol(secreto)]
Symbol.for(): registro global de sÃmbolos
Symbol.for(clave) busca en un registro global. Si ya existe un sÃmbolo con esa clave, lo devuelve; si no, lo crea. Permite compartir el mismo sÃmbolo entre módulos distintos:
// En módulo A:
const ID_A = Symbol.for('app:id');
// En módulo B (otra parte del código):
const ID_B = Symbol.for('app:id');
console.log(ID_A === ID_B); // true — mismo sÃmbolo del registro global
// Symbol() siempre crea uno nuevo
const local1 = Symbol('local');
const local2 = Symbol('local');
console.log(local1 === local2); // false
// Symbol.keyFor() obtiene la clave del registro
console.log(Symbol.keyFor(ID_A)); // 'app:id'
console.log(Symbol.keyFor(local1)); // undefined (no está en el registro)
Well-known symbols: personalizar comportamientos del lenguaje
JavaScript usa un conjunto de sÃmbolos predefinidos (well-known symbols) para definir comportamientos que pueden personalizarse en tus objetos. Son propiedades estáticas de Symbol:
// Symbol.iterator: hacer 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() {
if (actual <= fin) {
return { value: actual++, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const rango = new Rango(1, 5);
console.log([...rango]); // [1, 2, 3, 4, 5]
for (const n of rango) console.log(n); // 1 2 3 4 5
Symbol.toPrimitive: controlar la conversión de tipos
Symbol.toPrimitive permite definir cómo un objeto se convierte a primitivo en distintos contextos:
class Dinero {
constructor(cantidad, moneda = 'EUR') {
this.cantidad = cantidad;
this.moneda = moneda;
}
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number': return this.cantidad;
case 'string': return `${this.cantidad} ${this.moneda}`;
default: return this.cantidad; // 'default' hint
}
}
}
const precio = new Dinero(99.99, 'EUR');
console.log(+precio); // 99.99 (hint: 'number')
console.log(`${precio}`); // '99.99 EUR' (hint: 'string')
console.log(precio + 0.01); // 100 (hint: 'default')
console.log(precio > 50); // true
Symbol.hasInstance y Symbol.toStringTag
// Symbol.hasInstance: personalizar instanceof
class EsArray {
static [Symbol.hasInstance](instancia) {
return Array.isArray(instancia);
}
}
console.log([] instanceof EsArray); // true
console.log({} instanceof EsArray); // false
// Symbol.toStringTag: personalizar Object.prototype.toString
class MiColeccion {
get [Symbol.toStringTag]() {
return 'MiColeccion';
}
}
const col = new MiColeccion();
console.log(Object.prototype.toString.call(col)); // '[object MiColeccion]'
console.log(col.toString()); // '[object MiColeccion]'
// Comparación con tipos nativos:
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call(null); // '[object Null]'
Object.prototype.toString.call(/re/); // '[object RegExp]'
Los sÃmbolos se usan principalmente en librerÃas y código de infraestructura donde necesitas añadir propiedades a objetos ajenos sin riesgo de colisión, o cuando quieres personalizar el comportamiento de tus objetos al interactuar con las operaciones del lenguaje. En código de aplicación, el uso más frecuente es Symbol.iterator para crear colecciones propias y Symbol.for() para compartir identificadores entre módulos.
