Symbols e seus casos de uso

1. Introdução ao Symbol: o tipo primitivo único

Symbol é o sétimo tipo primitivo do JavaScript, introduzido no ECMAScript 2015 (ES6). Diferente de strings ou números, cada Symbol é único e imutável, garantindo que nunca haverá colisão de identificadores.

// Criação básica
const sym1 = Symbol();
const sym2 = Symbol('minha descricao');

console.log(sym1); // Symbol()
console.log(sym2); // Symbol(minha descricao)
console.log(sym2.description); // 'minha descricao'

// Unicidade garantida
console.log(Symbol('id') === Symbol('id')); // false

// Symbol.for() cria/recupera do registry global
const globalSym = Symbol.for('app.config');
const sameGlobalSym = Symbol.for('app.config');
console.log(globalSym === sameGlobalSym); // true

A diferença crucial entre Symbol() e Symbol.for() é que o primeiro cria um símbolo único a cada chamada, enquanto o segundo mantém um registro global que permite compartilhar símbolos entre diferentes partes da aplicação.

2. Symbols como chaves de propriedades de objetos

Symbols são ideais para criar propriedades que não colidem com outras chaves, especialmente em bibliotecas ou frameworks.

const PROP_ID = Symbol('id');
const PROP_SECRET = Symbol('secret');

const usuario = {
  nome: 'João',
  [PROP_ID]: 12345,
  [PROP_SECRET]: 'dados-sensiveis'
};

// Propriedades com Symbol não aparecem em Object.keys()
console.log(Object.keys(usuario)); // ['nome']

// Acesso específico
console.log(Object.getOwnPropertySymbols(usuario)); // [ Symbol(id), Symbol(secret) ]
console.log(Reflect.ownKeys(usuario)); // [ 'nome', Symbol(id), Symbol(secret) ]

3. Symbols bem conhecidos (Well-known Symbols)

O JavaScript possui symbols integrados que permitem personalizar comportamentos fundamentais de objetos.

Symbol.iterator

const colecao = {
  itens: [10, 20, 30],
  [Symbol.iterator]: function* () {
    for (const item of this.itens) {
      yield item * 2;
    }
  }
};

for (const valor of colecao) {
  console.log(valor); // 20, 40, 60
}

Symbol.toPrimitive

class Temperatura {
  constructor(celsius) {
    this.celsius = celsius;
  }

  [Symbol.toPrimitive](hint) {
    if (hint === 'number') return this.celsius;
    if (hint === 'string') return `${this.celsius}°C`;
    return this.celsius > 30 ? 'quente' : 'frio';
  }
}

const temp = new Temperatura(35);
console.log(+temp); // 35
console.log(String(temp)); // '35°C'
console.log(temp + ''); // 'quente'

Symbol.hasInstance

class Validador {
  static [Symbol.hasInstance](instancia) {
    return instancia !== null && 
           typeof instancia === 'object' && 
           'validar' in instancia;
  }
}

const obj = { validar: () => true };
console.log(obj instanceof Validador); // true

4. Symbols para metadados e privacidade em Node.js

Em módulos Node.js, Symbols oferecem uma camada de proteção contra acesso acidental a propriedades internas.

// modulo-database.js
const _pool = Symbol('pool');
const _conexoes = Symbol('conexoes');

class Database {
  constructor() {
    this[_pool] = [];
    this[_conexoes] = 0;
  }

  conectar() {
    this[_conexoes]++;
    return Symbol('conexao-' + this[_conexoes]);
  }

  get status() {
    return {
      poolSize: this[_pool].length,
      conexoesAtivas: this[_conexoes]
    };
  }
}

module.exports = Database;

// Uso com Express
const Database = require('./modulo-database');
const db = new Database();
const connId = db.conectar();
console.log(db.status); // { poolSize: 0, conexoesAtivas: 1 }
// As propriedades privadas não são acessíveis externamente

5. Symbols no ecossistema React: chaves e estado interno

Symbols podem resolver problemas de colisão em componentes React e hooks personalizados.

import React, { createContext, useContext, useState } from 'react';

// Evitando colisão com props padrão
const PROP_INTERNO = Symbol('interno');

const MeuContexto = createContext();

function ProvedorPersonalizado({ children }) {
  const [estado, setEstado] = useState(null);

  const valor = {
    [PROP_INTERNO]: Symbol('instancia-unica'),
    dados: estado,
    atualizar: setEstado
  };

  return (
    <MeuContexto.Provider value={valor}>
      {children}
    </MeuContexto.Provider>
  );
}

function ComponenteConsumer() {
  const contexto = useContext(MeuContexto);

  // Acessa apenas dados públicos
  return <div>{contexto.dados}</div>;
}

6. Symbols e serialização: JSON, structuredClone e console

Symbols são omitidos durante serializações padrão, o que requer atenção especial.

const SECRET = Symbol('secret');
const obj = {
  nome: 'João',
  [SECRET]: 'senha123'
};

// JSON.stringify ignora Symbols
console.log(JSON.stringify(obj)); // {"nome":"João"}

// Solução com replacer function
function replacer(key, value) {
  if (typeof key === 'symbol') return undefined;
  return value;
}

// structuredClone também perde Symbols
const clone = structuredClone(obj);
console.log(clone[SECRET]); // undefined

// Alternativa: serialização manual
function serializarComSymbols(obj) {
  const symbols = Object.getOwnPropertySymbols(obj);
  const data = { ...obj };
  symbols.forEach(sym => {
    data[`__symbol_${sym.description}`] = obj[sym];
  });
  return JSON.stringify(data);
}

7. Boas práticas e padrões comuns com Symbols

Constantes de enumeração semânticas

const StatusPedido = {
  PENDENTE: Symbol('pendente'),
  PROCESSANDO: Symbol('processando'),
  CONCLUIDO: Symbol('concluido'),
  CANCELADO: Symbol('cancelado')
};

function processarPedido(status) {
  switch(status) {
    case StatusPedido.PENDENTE:
      return 'Aguardando pagamento';
    case StatusPedido.CONCLUIDO:
      return 'Pedido entregue';
    default:
      return 'Status desconhecido';
  }
}

Propriedades não enumeráveis com Object.defineProperty

const _metadados = Symbol('metadados');

class Logger {
  constructor() {
    Object.defineProperty(this, _metadados, {
      value: { criado: Date.now(), versao: '1.0' },
      enumerable: false,
      writable: false
    });
  }

  getMetadados() {
    return this[_metadados];
  }
}

Quando usar Symbols vs WeakMap vs strings

Abordagem Vantagem Desvantagem
Symbol Único, não serializa automaticamente Não é privado de verdade
WeakMap Privacidade real Mais complexo, não é primitivo
String Simples, serializável Propenso a colisões

Referências