TechLead

Symbols

Tipo primitivo Symbol, símbolos conocidos y casos de uso

¿Qué son los Symbols?

Symbol es un tipo primitivo introducido en ES6 que representa un identificador único. A diferencia de las cadenas, cada Symbol está garantizado de ser único, lo que los hace perfectos para claves de propiedades que no colisionarán con otras propiedades.

Características Clave

  • Único — Cada llamada a Symbol() crea un símbolo único
  • Inmutable — No puede cambiarse una vez creado
  • No enumerable — Oculto de for...in y Object.keys()
  • Descripción — Cadena opcional para propósitos de depuración

Creando Symbols

// Crear un símbolo
const sym1 = Symbol();
const sym2 = Symbol();

console.log(sym1 === sym2); // false — siempre único

// Symbol con descripción (para depuración)
const id = Symbol("id");
console.log(id.toString());    // "Symbol(id)"
console.log(id.description);   // "id"

// Misma descripción, aún símbolos diferentes
const a = Symbol("nombre");
const b = Symbol("nombre");
console.log(a === b); // false

// ⚠️ No se puede usar 'new'
new Symbol(); // TypeError: Symbol no es un constructor

Symbols como Claves de Propiedades

const ID = Symbol("id");
const SECRETO = Symbol("secreto");

const usuario = {
  nombre: "Alicia",
  [ID]: 12345,
  [SECRETO]: "contraseña123"
};

// Acceder con el símbolo
console.log(usuario[ID]);     // 12345
console.log(usuario[SECRETO]); // "contraseña123"

// Las propiedades Symbol están ocultas de la enumeración normal
console.log(Object.keys(usuario));        // ["nombre"]
console.log(JSON.stringify(usuario));     // {"nombre":"Alicia"}

for (const clave in usuario) {
  console.log(clave);  // Solo "nombre"
}

// Pero pueden accederse con:
console.log(Object.getOwnPropertySymbols(usuario)); // [Symbol(id), Symbol(secreto)]
console.log(Reflect.ownKeys(usuario)); // ["nombre", Symbol(id), Symbol(secreto)]

Symbol.for() - Registro Global

// Crear/recuperar del registro de símbolos global
const globalSym1 = Symbol.for("app.id");
const globalSym2 = Symbol.for("app.id");

console.log(globalSym1 === globalSym2); // true — ¡mismo símbolo!

// Obtener la clave para un símbolo global
console.log(Symbol.keyFor(globalSym1)); // "app.id"

// Los símbolos regulares no están en el registro
const symLocal = Symbol("local");
console.log(Symbol.keyFor(symLocal)); // undefined

// Caso de uso: Compartir símbolos entre módulos/iframes
// En el módulo A:
const CLAVE_COMPARTIDA = Symbol.for("miApp.claveCompartida");

// En el módulo B:
const clave = Symbol.for("miApp.claveCompartida");
// clave === CLAVE_COMPARTIDA

Símbolos Conocidos (Well-Known Symbols)

JavaScript tiene símbolos integrados que personalizan el comportamiento de objetos:

// Symbol.iterator - Hacer objetos iterables
const rango = {
  inicio: 1,
  fin: 5,
  
  [Symbol.iterator]() {
    let actual = this.inicio;
    const fin = this.fin;
    
    return {
      next() {
        if (actual <= fin) {
          return { value: actual++, done: false };
        }
        return { done: true };
      }
    };
  }
};

console.log([...rango]); // [1, 2, 3, 4, 5]

// Symbol.toStringTag - Personalizar Object.prototype.toString
class MiClase {
  get [Symbol.toStringTag]() {
    return "MiClase";
  }
}

console.log(Object.prototype.toString.call(new MiClase())); 
// "[object MiClase]"

// Symbol.toPrimitive - Controlar conversión de tipos
const dinero = {
  cantidad: 100,
  moneda: "USD",
  
  [Symbol.toPrimitive](pista) {
    if (pista === "number") return this.cantidad;
    if (pista === "string") return `${this.cantidad} ${this.moneda}`;
    return this.cantidad; // predeterminado
  }
};

console.log(+dinero);        // 100
console.log(`${dinero}`);    // "100 USD"
console.log(dinero + 50);    // 150

Más Símbolos Conocidos

// Symbol.hasInstance - Personalizar instanceof
class MiArray {
  static [Symbol.hasInstance](instancia) {
    return Array.isArray(instancia);
  }
}

console.log([] instanceof MiArray);     // true
console.log({} instanceof MiArray);     // false

// Symbol.species - Constructor para objetos derivados
class MiArrayExtendido extends Array {
  static get [Symbol.species]() {
    return Array; // map(), filter() retornan Arrays regulares
  }
}

const arr = new MiArrayExtendido(1, 2, 3);
const mapeado = arr.map(x => x * 2);

console.log(mapeado instanceof MiArrayExtendido); // false
console.log(mapeado instanceof Array);   // true

// Symbol.isConcatSpreadable
const arr1 = [1, 2];
const noExpandible = {
  0: 3,
  1: 4,
  length: 2,
  [Symbol.isConcatSpreadable]: false
};

console.log(arr1.concat(noExpandible)); // [1, 2, {0: 3, 1: 4, ...}]

noExpandible[Symbol.isConcatSpreadable] = true;
console.log(arr1.concat(noExpandible)); // [1, 2, 3, 4]

Casos de Uso Prácticos

// 1. Propiedades tipo privado (antes de la sintaxis #private)
const _saldo = Symbol("saldo");

class CuentaBancaria {
  constructor(inicial) {
    this[_saldo] = inicial;
  }
  
  depositar(cantidad) {
    this[_saldo] += cantidad;
  }
  
  obtenerSaldo() {
    return this[_saldo];
  }
}

const cuenta = new CuentaBancaria(100);
console.log(cuenta.obtenerSaldo()); // 100
console.log(cuenta._saldo);         // undefined
console.log(Object.keys(cuenta));   // []

// 2. Evitar colisión de propiedades en mixins
const volable = (() => {
  const VELOCIDAD_VUELO = Symbol("velocidadVuelo");
  
  return {
    [VELOCIDAD_VUELO]: 100,
    volar() {
      console.log(`Volando a ${this[VELOCIDAD_VUELO]} mph`);
    }
  };
})();

// 3. Marcado de tipo (Type branding)
const TipoCadena = Symbol("Cadena");
const TipoNumero = Symbol("Numero");

function marcado(valor, tipo) {
  return { valor, [tipo]: true };
}

const str = marcado("hola", TipoCadena);
console.log(str[TipoCadena]); // true
console.log(str[TipoNumero]); // undefined

💡 Puntos Clave

  • • Symbol() crea identificadores únicos e inmutables
  • • Usa símbolos para claves de propiedades para evitar colisiones
  • • Symbol.for() crea símbolos globales compartidos
  • • Los símbolos conocidos (Symbol.iterator, etc.) personalizan el comportamiento de objetos
  • • Las propiedades Symbol están ocultas de la enumeración normal
  • • Excelentes para bibliotecas y frameworks para evitar conflictos

Práctica y patrones de uso

  • • Añade una propiedad basada en Symbol a un objeto y confirma que permanece oculta de Object.keys.
  • • Implementa [Symbol.iterator] en una colección personalizada y asegúrate de que for...of funcione.
  • • Usa Symbol.toPrimitive para formatear valores de moneda y prueba las rutas de coerción string/number.
  • • Crea un registro con Symbol.for y demuestra cómo dos archivos comparten la misma clave de símbolo.