Los Symbol son un tipo primitivo de JavaScript introducido en ES6 que genera valores únicos e irrepetibles. Cada llamada a Symbol() produce un valor distinto, incluso con la misma descripción. Son la herramienta correcta cuando necesitas claves de objeto que no colisionen con ninguna propiedad existente o futura, y los well-known symbols permiten personalizar comportamientos profundos del lenguaje.
Creación y unicidad
const s1 = Symbol('descripcion');
const s2 = Symbol('descripcion');
console.log(s1 === s2); // false siempre únicos
console.log(typeof s1); // "symbol"
console.log(s1.toString()); // "Symbol(descripcion)"
console.log(s1.description); // "descripcion" (ES2019)
Symbols como claves de objeto
Al usar un Symbol como clave, la propiedad queda oculta a for...in, Object.keys() y JSON.stringify(). Solo es accesible si tienes la referencia al Symbol original:
const ID = Symbol('id');
const PERMISOS = Symbol('permisos');
const usuario = {
nombre: 'Ana',
[ID]: 'usr-001',
[PERMISOS]: ['leer', 'escribir'],
};
console.log(Object.keys(usuario)); // ['nombre']
console.log(JSON.stringify(usuario)); // '{"nombre":"Ana"}'
console.log(usuario[ID]); // 'usr-001'
// Para obtener las claves Symbol de un objeto:
console.log(Object.getOwnPropertySymbols(usuario)); // [Symbol(id), Symbol(permisos)]
Symbol.for: registro global compartido
Symbol.for(clave) busca en el registro global y devuelve el mismo Symbol si ya existe con esa clave. Es la forma de compartir Symbols entre módulos o iframes:
const A = Symbol.for('app.id');
const B = Symbol.for('app.id');
console.log(A === B); // true mismo del registro global
console.log(Symbol.keyFor(A)); // "app.id"
// A diferencia de Symbol() normal:
const local = Symbol('app.id');
console.log(Symbol.keyFor(local)); // undefined no está en el registro
Symbol.iterator: hacer objetos iterables
Implementar Symbol.iterator en un objeto lo hace iterable con for...of, el operador spread y la desestructuración de arrays:
class Rango {
constructor(inicio, fin, paso = 1) {
this.inicio = inicio;
this.fin = fin;
this.paso = paso;
}
[Symbol.iterator]() {
let actual = this.inicio;
const { fin, paso } = this;
return {
next() {
if (actual <= fin) {
const value = actual;
actual += paso;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
}
}
const pares = new Rango(2, 10, 2);
console.log([...pares]); // [2, 4, 6, 8, 10]
for (const n of new Rango(1, 5)) {
process.stdout.write(n + ' '); // 1 2 3 4 5
}
Symbol.toPrimitive: conversión controlada
Symbol.toPrimitive permite definir cómo se convierte un objeto a un primitivo según el contexto ("number", "string" o "default"):
class Dinero {
constructor(cantidad, moneda) {
this.cantidad = cantidad;
this.moneda = moneda;
}
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.cantidad;
if (hint === 'string') return `${this.cantidad} ${this.moneda}`;
// "default": usado en operaciones como + con string
return this.cantidad;
}
}
const precio = new Dinero(42.99, 'EUR');
console.log(+precio); // 42.99 (hint: "number")
console.log(`Total: ${precio}`); // "Total: 42.99 EUR" (hint: "string")
console.log(precio + 10); // 52.99 (hint: "default")
Symbol.hasInstance: control de instanceof
Symbol.hasInstance en una clase estática permite personalizar el comportamiento del operador instanceof:
class EsNumerico {
static [Symbol.hasInstance](valor) {
return typeof valor === 'number' || (
typeof valor === 'string' && !isNaN(Number(valor)) && valor.trim() !== ''
);
}
}
console.log(42 instanceof EsNumerico); // true
console.log('3.14' instanceof EsNumerico); // true
console.log('hola' instanceof EsNumerico); // false
console.log(null instanceof EsNumerico); // false
Otros well-known symbols útiles
Hay otros Symbols predefinidos que permiten personalizar distintos aspectos del motor:
// Symbol.toStringTag: personaliza Object.prototype.toString
class ColeccionPersonalizada {
get [Symbol.toStringTag]() {
return 'ColeccionPersonalizada';
}
}
const col = new ColeccionPersonalizada();
console.log(Object.prototype.toString.call(col)); // "[object ColeccionPersonalizada]"
// Symbol.isConcatSpreadable: controla cómo se comporta en Array.prototype.concat
const puntosExtra = [4, 5, 6];
puntosExtra[Symbol.isConcatSpreadable] = false;
console.log([1, 2, 3].concat(puntosExtra)); // [1, 2, 3, [4, 5, 6]]
// Symbol.species: controla qué constructor se usa en métodos que devuelven nuevas instancias
class MiArray extends Array {
static get [Symbol.species]() { return Array; }
}
const m = new MiArray(1, 2, 3);
console.log(m.map(x => x * 2) instanceof MiArray); // false devuelve Array normal
Los Symbols son la solución idiomática de JavaScript para añadir metadatos o comportamientos personalizados a objetos sin riesgo de colisión con código existente o futuro. Los well-known symbols, en particular, son la API que el motor usa para exponer puntos de extensión del lenguaje de forma formal y predecible.
