Lucas Santos
May 29, 2018 · 7 min read

Depois de algum tempo fora do ar, volto para apresentar a vocês um mistério, na verdade, nem tanto mistério, mas um tipo primitivo do Javascript que já existe há um tempo, mas nunca ficamos sabendo deles. Os Symbols.

Symbols? É de comer?

Symbols são um tipo primitivo do Javascript desde o ES6, mas eles já existiam antes disso. Symbols são usados para definir valores chaves para o funcionamento da linguagem há muito tempo, mas eles não eram acessíveis por ninguém, somente pelo próprio runtime e pelo interpretador. O que mudou foi que o ES6 tornou possível que nós, meros programadores, pudéssemos acessar esses tipos.

Symbols são caracterizados como uma espécie de "metaprogramação", que foca principalmente em poder acessar aspectos do core da linguagem. Podemos criar um Symbol dessa forma:

Perceba que não usamos nenhuma keyword new para definir um Symbol. Isto porque eles não são instâncias de nada, na verdade, se tentarmos fazer algo deste tipo:

Vamos ter um TypeError na nossa cara.

Symbols podem ter uma descrição para propósitos de debugging, então podemos fazer algo assim:

Ok, entendemos como podemos criar um, mas o que eles são de verdade? Symbols são tipos imutáveis nativos, assim como números ou strings. Mas temos que notar algo diferente, Symbols são únicos, diferentemente da maioria das primitivas que temos por ai.

Por serem únicos, ao compararmos eles, vamos obter resultados bastante interessantes:

Isto acontece porque cada símbolo tem um identificador único que não pode ser alterado, e é este identificador que faz a comparação entre eles. Da mesma forma, se utilizarmos typeof Symbol() ou typeof Symbol('desc') vamos sempre obter symbol .

Symbols são acessíveis de três formas que vamos explorar mais abaixo, mas tenha em mente algumas informações importantes:

  • Symbols são acessíveis entre realms, por realm queremos dizer contexto, por exemplo, sua página é um contexto de document enquanto, dentro dela, podemos ter um <iframe> com um contexto/realm diferente.
  • Você pode registrar Symbols globais e acessa-los por entre esses contextos também
  • Uma das classes de Symbols é chamada de "Well-Known", eles existem entre realms, mas não são acessíveis no registro global de Symbols

"Runtime-wide" — Acessível pelo runtime

Existem duas formas que você pode utilizar para adicionar Symbols no escopo do runtime do sistema, ambas são através de métodos existentes dentro das APIs do próprio Symbol, o primeiro éSymbol.for(chave) .

Basicamente o que este método faz é verificar se existe um Symbol no registro global que seja associado a esta chave, se existir, ele é retornado, se não, ele é criado e retornado. Então vamos estudar um pouco as chamadas:

Veja que arranjamos um jeito de buscar os símbolos, mesmo eles sendo únicos. Isto acontece porque o registro global de Symbols mantém uma chave para cada Symbol criado, e isto é o identificador que podemos utilizar para encontrar esses valores neste escopo.

Note somente que, quando criamos uma "chave" usando Symbol.for('chave') , este valor se torna também nossa descrição.

Symbols estão em todos os lugares

Outra coisa importantíssima de se notar é que Symbols é o mais global que você vai chegar de qualquer outro tipo de variável global, porque além de eles serem acessíveis no seu código e no seu "realm" eles também estão disponíveis em contextos alheios. Então tome muito cuidado ao criar Symbols usando nomes genéricos como "user", "sort" e coisas do tipo, porque você pode acabar sobrescrevendo um valor importante do sistema.

Symbol.keyFor

De forma análoga, podemos encontrar a chave que foi associada a um Symbol através do método Symbol.keyFor(symbol) que, dado um Symbol criado previamente, vai nos retornar a chave associada a ele:

Dizer "Runtime-wide" significa simplesmente dizer que vamos poder acessar estes Symbols entre contextos, mais ou menos assim:

Veja que podemos acessar o mesmo Symbol de dentro de outra window presente no iframe.

"Well-known" — Os originais

Well known, traduzido, significa "Bem conhecido", mas, na verdade, esses Symbols não tem nada de conhecidos. Eles só são chamados assim porque fazem parte do core da linguagem, são built-ins. Antes do ES6, nenhum desses Symbols eram expostos, mas agora podemos brincar com eles.

Um grande exemplo foi utilizado em outro artigo sobre iterators que escrevi. Ele é utilizado para definir o método@@iterator em objetos que são aderentes ao protocolo de iteração. Além deste existe uma lista de Well-Known Symbols na MDN, muitos deles são bastante conhecidos pela gente como o replace ou o match .

Symbol.match

De acordo com a documentação, podemos setar o Symbol.match em expressões regulares para false , de forma que faremos elas se comportarem como strings literais quando estamos comparando uns com os outros (principalmente na hora de usar métodos como startsWith , endsWith e includes ):

Ou, podemos simplesmente usar o toString() que temos o mesmo resultado:

Outros usos legais destes Symbols podem ser encontrados aqui.

Acesso global

Well known Symbols são únicos, mas são compartilhados entre contextos, mesmo que eles não sejam acessíveis através do registro global que comentamos acima, vamos usar o mesmo código:

No entanto não podemos fazer isso:

Iterando por Symbols

Um uso interessante deste tipo é que eles podem ser usados como chaves de objetos, mais ou menos dessa forma:

Ao executarmos console.log(foo) ou qualquer coisa do tipo, vamos obter a representação em string desse objeto, já que os consoles estão inteligentes o suficiente para buscar essa informação. No entanto, se rodarmos Object.keys(foo) vamos ter somente ['propriedade'] . Isto porque o método keys de Object ignora completamente os Symbols. O mesmo vale para JSON.stringify , que vai simplesmente nos retornar {"propriedade": "legal"} .

Podemos ir ainda mais longe tentando usar um for ... in , ou até um Object.getOwnPropertyNames(foo) , mas todos eles vão ignorar os Symbols. Se você quiser realmente buscar esses valores, você precisa saber o que está procurando. Existe um método próprio para iterar por Symbols em um objeto, que é o Object.getOwnPropertySymbols . Se executarmos o trecho abaixo, vamos ter uma lista de todos os Symbols que criamos e poderemos iterar por eles, mais ou menos dessa forma:

Casos de uso

Até aqui só falamos muito sobre Symbols, mas para que raios você vai usar essa coisa!?

Temos alguns casos mais comuns que vou explicar por cima:

  • Conflito de nomes: Neste caso podemos tirar proveito da natureza única do Symbol para evitar nomes repetidos em chaves de objetos, assim como fizemos no exemplo anterior.
  • Privacidade: Se estamos criando um framework ou uma lib que precisa de algumas propriedades bem seguras, podemos tirar proveito de que nenhuma propriedade iterável que é largamente conhecida chega a iterar por Symbols, então podemos criar um objeto de configurações (ou metadados), por exemplo, e colocar vários Symbols por lá para definirmos nossas diretrizes de operação. Basta que você itere por ela usando Object.getOwnPropertySymbols .
  • Constantes: Geralmente usamos uma string ou número para definir uma constante, por exemplo, o nível de um log, que pode ser definido como log.levels = { INFO: 'info', DEBUG: 'debug' } mas isso não os torna realmente globais seguros, porque podemos criar outros valores iguais e, ao invés de usar nossa função como log(log.levels.DEBUG, 'mensagem') podemos simplesmente fazer log('debug', 'mensagem') . Então podemos utilizar Symbols como log.levels = { INFO: Symbol('info'), DEBUG: Symbol('debug') } para prevenir isso

Em suma, Symbols são uma ótima maneira de atrelar propriedades em objetos de uma forma simples, ou então de criar diretrizes que seu programa deve seguir por baixo dos panos.

Se eu for ficar falando de todos os casos de uso de Symbols, provavelmente este artigo ficará imenso, então vou me ater a um último caso de uso que merece um pouco de explicação.

Definir protocolos e Hooks

Este é o maior caso de uso e, com certeza o mais legal de todos. Hoje, muitas libs populares utilizam protocolos sobre os quais alguns objetos devem aderir (como você pode ver aqui), não precisamos ir muito longe, o próprio Symbol.iterator é um protocolo a qual todos os Arrays aderem, e isto define que o objeto pode ser iterado.

Basicamente, ao executar um for ... of o Javascript faz uma checagem se Object[Symbol.iterator] existe, se sim, então parabéns, você tem um iterador, se não, você não pode iterar por esse objeto.

Vamos usar um caso mais interessante, se quisermos criar um protocolo que diz de qual forma um objeto deve ser transformado em string para ser printado na tela (basicamente o que o toString() faz). Podemos criar um Symbol.for('object.upper') e colocar este Symbol em qualquer um dos nossos objetos:

O exemplo anterior não irá funcionar porque não implementamos a lógica no console e nem no object.keys para olhar o valor de obj[Symbol.for('object.upper')] e printar tudo em maiúsculo, mas deu para entender a ideia. Uma implementação disto para o console seria algo assim:

Nada nos impede de adicionar uma função em um Symbol dentro de um objeto, criando o que é chamado de hook, funções que executam em determinadas partes do código respondendo a determinados métodos. Podemos, por exemplo, alterar o comportamento de como o console itera e inspeciona objetos por uma função própria, que é chamada sempre que o objeto é convertido para string.

Conclusão

Symbols são obscuros e achar uma utilidade para eles é realmente muito complicado, provavelmente ninguém vai ter que se preocupar em utilizar ou mexer em Symbols tão cedo.

No entanto, o entendimento que o Symbol proporciona para o programador acerca do funcionamento da linguagem é fora do normal. Apenas entendendo os iterators, somos capazes de deduzir como loops como for e for ... of funcionam sem precisar de muito esforço. Tornando nosso código muito mais interessante.

Até mais!

Training Center

Conectamos pessoas que querem aprender algo relacionado a desenvolvimento de software com gente que pode guiá-las.

Lucas Santos

Written by

Brazilian Programmer, fulltime geek. Loves to write docs and clean code. Dislikes LoDash.

Training Center

Conectamos pessoas que querem aprender algo relacionado a desenvolvimento de software com gente que pode guiá-las.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade