Qual é a motivação para trazer Símbolos para o ES6?

368

ATUALIZAÇÃO : Recentemente, surgiu um artigo brilhante da Mozilla . Leia se você estiver curioso.

Como você deve saber, eles estão planejando incluir um novo tipo primitivo Symbol no ECMAScript 6 (para não mencionar outras coisas malucas). Eu sempre pensei que a :symbolnoção em Ruby é desnecessária; em vez disso, poderíamos facilmente usar strings simples, como fazemos em JavaScript. E agora eles decidem complicar as coisas em JS com isso.

Eu não entendo a motivação. Alguém poderia me explicar se realmente precisamos de símbolos em JavaScript?

Yanis
fonte
6
Não sei como essa explicação é autêntica, mas é um começo: tc39wiki.calculist.org/es6/symbols .
Felix Kling 12/02
8
Os símbolos permitem tanto que permitem identificadores exclusivos com escopo definido nos objetos. Por exemplo, ter propriedades em objetos que são acessíveis apenas em um local.
Benjamin Gruenbaum
5
Não tenho certeza sobre isso, pois você pode usar Object.getOwnPropertySymbols (o)
Yanis
4
É mais singularidade do que privacidade.
Qantas 94 Heavy
2
Eles teriam uma implementação de classe mais complicada com palavras-chave privatee publicatributos de classe que decidiram abandonar para uma implementação de classe mais simples. Em vez de this.x = xvocê deveria fazer public x = xe para variáveis ​​privadas private y = y. Eles decidiram abandonar isso para uma implementação de classe muito mais mínima. O Symbol seria uma solução alternativa necessária para obter propriedades particulares na implementação mínima.
Lyschoening

Respostas:

224

A motivação original para introduzir símbolos no Javascript era ativar propriedades privadas .

Infelizmente, eles acabaram sendo severamente rebaixados. Eles não são mais particulares, pois você pode encontrá-los por meio de reflexão, por exemplo, usando Object.getOwnPropertySymbolsou proxies.

Agora eles são conhecidos como símbolos exclusivos e seu único objetivo é evitar conflitos de nomes entre propriedades. Por exemplo, o próprio ECMAScript agora pode introduzir ganchos de extensão por meio de certos métodos que você pode colocar em objetos (por exemplo, para definir seu protocolo de iteração) sem correr o risco de colidir com nomes de usuário.

Se isso é forte o suficiente, uma motivação para adicionar símbolos ao idioma é discutível.

Andreas Rossberg
fonte
93
A maioria dos idiomas (todos os principais da afaik) fornece algum mecanismo, geralmente reflexão, para obter acesso ao privado de qualquer maneira.
Esailija 14/05
19
@ Esailija, eu não acho que isso seja verdade - em particular, já que muitos idiomas não oferecem reflexão em primeiro lugar. Vazar estado privado através da reflexão (como, por exemplo, em Java) deve ser considerado um bug, não um recurso. Isso é especialmente verdadeiro nas páginas da Web, onde ter um estado privado confiável pode ser relevante para a segurança. Atualmente, a única maneira de alcançá-lo no JS é através de fechamentos, que podem ser tediosos e dispendiosos.
Andreas Rossberg 14/05
38
O mecanismo não precisa ser reflexo - C ++, Java, C #, Ruby, Python, PHP, Objective-C, todos permitem o acesso de uma maneira ou de outra, se alguém realmente quiser. Não se trata realmente de habilidade, mas de comunicação.
Esailija 14/05
4
O @plalx, ​​na Web, às vezes, o encapsulamento também envolve segurança.
Andreas Rossberg
3
@RolandPihlakas, infelizmente, Object.getOwnPropertySymbolsnão é o único vazamento; o mais difícil é a capacidade de usar proxies para interceptar o acesso a uma propriedade "privada".
Andreas Rossberg
95

Os símbolos não garantem privacidade verdadeira, mas podem ser usados ​​para separar propriedades públicas e internas de objetos. Vamos dar um exemplo em que podemos usar Symbolpara ter propriedades privadas.

Vamos dar um exemplo em que a propriedade de um objeto não é privada.

var Pet = (function() {
  function Pet(type) {
    this.type = type;
  }
  Pet.prototype.getType = function() {
    return this.type;
  }
  return Pet;
}());

var a = new Pet('dog');
console.log(a.getType());//Output: dog
a.type = null;
//Modified outside
console.log(a.getType());//Output: null

Acima, a Petpropriedade da classe typenão é privada. Para torná-lo privado, precisamos criar um fechamento. O exemplo abaixo ilustra como podemos tornar typeprivados usando um fechamento.

var Pet = (function() {
  function Pet(type) {
    this.getType = function(){
      return type;
    };
  }
  return Pet;
}());

var b = new Pet('dog');
console.log(b.getType());//dog
b.type = null;
//Stays private
console.log(b.getType());//dog

Desvantagem da abordagem acima: Estamos introduzindo um fechamento extra para cada Petinstância criada, o que pode prejudicar o desempenho.

Agora nós apresentamos Symbol. Isso pode nos ajudar a tornar uma propriedade privada sem o uso de fechamentos desnecessários extras. Exemplo de código abaixo:

var Pet = (function() {
  var typeSymbol = Symbol('type');
  function Pet(type) {
    this[typeSymbol] = type;
  }
  Pet.prototype.getType = function(){
    return this[typeSymbol];
  }
  return Pet;
}());

var a = new Pet('dog');
console.log(a.getType());//Output: dog
a.type = null;
//Stays private
console.log(a.getType());//Output: dog
Samar Panda
fonte
15
Observe que as propriedades do símbolo não são particulares ! Os símbolos são livres de colisões . Você pode querer ler a resposta aceita.
Bergi
3
Sim, o símbolo não garante a verdadeira privacidade, mas pode ser usado para separar propriedades públicas e internas dos objetos. Desculpe, esqueci de adicionar este ponto à minha resposta. Atualizará minha resposta de acordo.
Samar Panda
@SamarPanda, você também pode dizer que prefixar membros com _não garante privacidade verdadeira, mas pode ser usado para separar propriedades públicas e internas de objetos. Em outras palavras, resposta inútil.
Pacerier 22/03
10
Eu não diria inútil, como símbolos são, por padrão, não enumeráveis, também não pode ser acessado por 'erro', enquanto qualquer outra tecla pode.
Patrick
5
Acho a sua resposta a única que realmente tem um exemplo que faz sentido, por que você deseja definir o atributo privado do objeto como um símbolo, em vez de apenas um atributo normal.
Luis Lobo Borobia
42

Symbolssão um novo tipo especial de objeto que pode ser usado como um nome de propriedade exclusivo nos objetos. Usar em Symbolvez de string's permite que diferentes módulos criem propriedades que não entrem em conflito. Symbolstambém podem ser tornados privados, para que suas propriedades não possam ser acessadas por qualquer pessoa que ainda não tenha acesso direto aoSymbol .

Symbolssão um novo primitivo . Assim como as number, stringe booleanprimitivos, Symboltêm uma função que pode ser usado para criá-los. Diferente das outras primitivas, Symbolsnão possui uma sintaxe literal (por exemplo, como stringpossui '') - a única maneira de criá-las é com o Symbolconstrutor da seguinte maneira:

let symbol = Symbol();

Na realidade, Symbolsão apenas uma maneira ligeiramente diferente de anexar propriedades a um objeto - você pode facilmente fornecer os Symbolsmétodos padrão conhecidos , exatamente como os Object.prototype.hasOwnPropertyque aparecem em tudo o que herda Object.

Aqui estão alguns dos benefícios do Symboltipo primitivo.

Symbols possui depuração incorporada

Symbols pode receber uma descrição, que é realmente usada apenas para depuração para facilitar a vida ao fazer o logon em um console.

Symbolspode ser usado como Objectchaves

É aqui que Symbolas coisas ficam realmente interessantes. Eles estão fortemente entrelaçados com objetos. Symbolpodem ser atribuídos como chaves a objetos, o que significa que você pode atribuir um número ilimitado de itens exclusivos Symbola um objeto e garantir que eles nunca entrem em conflito com as stringchaves ou outros itens exclusivos.Symbols .

Symbols pode ser usado como um valor único.

Vamos supor que você tem uma biblioteca de registro, que inclui vários níveis de log, como logger.levels.DEBUG, logger.levels.INFO, logger.levels.WARNe assim por diante. No código ES5, você gostaria de criar esses strings (so logger.levels.DEBUG === 'debug') ou numbers ( logger.levels.DEBUG === 10). Ambos não são ideais, pois esses valores não são únicos, mas Symbols são! Então logger.levelssimplesmente se torna:

log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn'),
};
log(log.levels.DEBUG, 'debug message');
log(log.levels.INFO, 'info message');

Leia mais neste ótimo artigo .

Mihai Alexandru-Ionut
fonte
10
Não sei se entendi o seu exemplo e por que você precisaria log.levels = {DEBUG: Symbol('debug')e não simplesmente log.levels = {DEBUG:'debug'}. no final, é o mesmo. Eu acho que vale a pena mencionar que os símbolos são invisíveis quando iteram sobre as chaves de um objeto. essa é a "coisa" deles
vsync
Um benefício é que alguém não pode usar acidentalmente um literal e acreditar que funcionaria para sempre. (Observe que esse não é um argumento muito forte, pois é possível simplesmente usar {}e obter o mesmo resultado (como valor exclusivo), ou talvez um literal seja o preferido nesse projeto, ou você pode dizer que é necessário ler o documento primeiro. pessoalmente acho que fornecem uma boa legibilidade do sentido único no código
apple
observe que, quando usado como valor exclusivo, o literal do objeto também possui depuração incorporada, ou Symbol("some message")seja {message:'some message'}, torna-se , sem dúvida o objeto se sai melhor aqui, pois você pode adicionar vários campos.
apple apple
38

Este post é sobre o Symbol(), fornecido com exemplos reais que eu poderia encontrar / criar e fatos e definições que eu poderia encontrar.

TLDR;

O Symbol()é o tipo de dados, introduzido com o lançamento do ECMAScript 6 (ES6).

Existem dois fatos curiosos sobre o símbolo.

  • o primeiro tipo de dados e apenas o tipo de dados em JavaScript que não possui literal

  • qualquer variável, definida com Symbol(), obtém conteúdo exclusivo, mas não é realmente privado .

  • qualquer dado possui seu próprio símbolo e, para os mesmos dados, os símbolos seriam os mesmos . Mais informações no parágrafo a seguir, caso contrário, não é um TLRD; :)

Como inicializo o símbolo?

1. Para obter um identificador exclusivo com um valor depurável

Você pode fazê-lo desta maneira:

var mySymbol1 = Symbol();

Ou assim:

var mySymbol2 = Symbol("some text here");

A "some text here"cadeia não pode ser extraída do símbolo, é apenas uma descrição para fins de depuração. Não muda o comportamento do símbolo de forma alguma. No entanto, você pode console.log(o que é justo, já que o valor é para depuração, para não confundir esse log com outra entrada de log):

console.log(mySymbol2);
// Symbol(some text here)

2. Para obter um símbolo para alguns dados de string

Nesse caso, o valor do símbolo é realmente levado em consideração e, dessa forma, dois símbolos podem não ser exclusivos.

var a1 = Symbol.for("test");
var a2 = Symbol.for("test");
console.log(a1 == a2); //true!

Vamos chamar esses símbolos de "segundo tipo". Eles não se cruzam com os símbolos do "primeiro tipo" (isto é, os definidos com Symbol(data)) de forma alguma.

Os próximos dois parágrafos pertencem apenas ao símbolo do primeiro tipo .

Como me beneficio do uso do Symbol em vez dos tipos de dados mais antigos?

Vamos primeiro considerar um objeto, um tipo de dados padrão. Poderíamos definir alguns pares de valores-chave e ter acesso aos valores especificando a chave.

var persons = {"peter":"pan","jon":"doe"};
console.log(persons.peter);
// pan

E se tivermos duas pessoas com o nome Peter?

Fazendo isso:

var persons = {"peter":"first", "peter":"pan"};

não faria muito sentido.

Portanto, parece ser um problema de duas pessoas absolutamente diferentes com o mesmo nome. Vamos então nos referir a novos Symbol(). É como uma pessoa na vida real - qualquer pessoa é única , mas seus nomes podem ser iguais. Vamos definir duas "pessoas".

 var a = Symbol("peter");
 var b = Symbol("peter");

Agora temos duas pessoas diferentes com o mesmo nome. Nossas pessoas são realmente diferentes? Eles são; você pode verificar isso:

 console.log(a == b);
 // false

Como nos beneficiamos lá?

Podemos fazer duas entradas no seu objeto para as diferentes pessoas e elas não podem ser confundidas de forma alguma.

 var firstPerson = Symbol("peter");
 var secondPerson = Symbol("peter");
 var persons = {[firstPerson]:"first", [secondPerson]:"pan"};

Nota:
Vale a pena notar, no entanto, que a string do objeto JSON.stringifydescartará todos os pares inicializados com um símbolo como chave.
A execução Object.keystambém não retornará esses Symbol()->valuepares.

Usando esta inicialização, é absolutamente impossível confundir as entradas para a primeira e a segunda pessoas. Chamar console.logpor eles produzirá corretamente seus segundos nomes.

 console.log(persons[a]);
 // first
 console.log(persons[b]);
 // pan

Quando usado no objeto, como ele é diferente em comparação à definição de propriedade não enumerável?

De fato, já existia uma maneira de definir uma propriedade a ser oculta Object.keyse enumerada. Aqui está:

var anObject = {};
var fruit = "apple";    

Object.defineProperty( anObject, fruit, {
    enumerable: false,
    value: "green"
});

Que diferença Symbol()traz para lá? A diferença é que você ainda pode obter a propriedade definida Object.definePropertyda maneira usual:

console.log(anObject[fruit]); //green
console.log(anObject["apple"]); //green
console.log(anObject.apple); //green

E se definido com o símbolo como no parágrafo anterior:

fruit = Symbol("apple");

Você terá a capacidade de receber seu valor somente se conhecer sua variável, ou seja,

console.log(anObject[fruit]); //green
console.log(anObject["apple"]); //undefined
console.log(anObject.apple); //undefined

Além disso, definir outra propriedade sob a chave "apple"fará com que o objeto descarte a mais antiga (e, se for codificado, poderá gerar um erro). Então, não há mais maçãs! É uma pena. Referindo-se ao parágrafo anterior, os Símbolos são únicos e definem uma chave, o Symbol()que a tornará única.

Conversão e verificação de tipo

  • Ao contrário de outros tipos de dados, é impossível convertê-lo Symbol()em qualquer outro tipo de dados.

  • É possível "criar" um símbolo com base no tipo de dados primitivo chamando Symbol(data).

  • Em termos de verificação do tipo, nada muda.

    function isSymbol ( variable ) {
        return typeof someSymbol === "symbol";
    }
    
    var a_Symbol = Symbol("hey!");
    var totally_Not_A_Symbol = "hey";
    
    console.log(isSymbol(a_Symbol)); //true
    console.log(isSymbol(totally_Not_A_Symbol)); //false

nicael
fonte
Isso foi migrado da documentação do SO?
Knu
11
@KNU não era; Eu recolhi a informação e escreveu esta resposta me
nicael
Resposta realmente bonita!
Mihai Alexandru-Ionut
11
Ótima resposta no Symbol, no entanto, ainda não sei por que eu usaria objetos com chaves de símbolos em vez de uma matriz. Se eu tenho várias pessoas como {"peter": "pan"} {"john": "doe"}, é ruim para mim colocá-las em um objeto. Pelo mesmo motivo, não faço aulas com propriedades duplicadas, como personFirstName1, personFirstName2. Isso combinado com a incapacidade de restringi-lo, não vejo benefícios apenas desvantagens.
Eldo
18

Aqui está como eu vejo isso. Os símbolos fornecem 'um nível extra de privacidade', impedindo que as chaves / propriedades de um objeto sejam expostas por alguns métodos populares, como Object.keys () e JSON.stringify ().

var age = Symbol();  // declared in another module perhaps?
class Person {
   constructor(n,a){
      this.name = n;
      this[age] = a;  
   }
   introduce(){
       console.log(`My name is ${this.name}. I am ${this[age]-10}.`);
   }
}
var j = new Person('Jane',45);
j.introduce();  // My name is Jane. I am 35.
console.log(JSON.stringify(j)); // {"name":"Jane"}
console.log(Object.keys(j)); // ["name"]
console.log(j[age]); // 45   (well…only if you know the age in the first place…)

Embora dado um objeto em si, essas propriedades ainda possam ser expostas por meio de reflexão, proxy, Object.getOwnPropertySymbols () etc., não há meios naturais para acessá-los por meio de alguns métodos diretos, que às vezes podem ser suficientes da perspectiva de OOP.

Chong Lip Phang
fonte
2

Um símbolo JS é um novo tipo de dados primitivo. São tokens que servem como IDs únicos . Um símbolo pode ser criado usando o Symbolconstrutor. Tomemos, por exemplo, este trecho do MDN:

// The symbol constructor takes one optional argument, 
// the descriptions which is used for debugging only.
// Here are two symbols with the same description
let Sym1 = Symbol("Sym");
let Sym2 = Symbol("Sym");
  
console.log(Sym1 == Sym2); // returns "false"
// Symbols are guaranteed to be unique.
// Even if we create many symbols with the same description,
// they are different values.

Muitas vezes, é útil usar símbolos como chaves de propriedade de objeto exclusivas, por exemplo:

let obj = {};
let prop = Symbol();

obj[prop] = 123;  // the symbol prop is assigned 123
obj.prop  = 456;  // the string prop is assigned 456

console.log(obj.prop, obj[prop]); // logs 456, 123

Willem van der Veen
fonte
0

Os símbolos têm dois casos de uso principais:

  1. Propriedades do objeto "oculto". Se quisermos adicionar uma propriedade a um objeto que "pertença" a outro script ou biblioteca, podemos criar um símbolo e usá-lo como chave de propriedade. Uma propriedade simbólica não aparece emfor..in , portanto não será processada acidentalmente junto com outras propriedades. Também não será acessado diretamente, porque outro script não possui o nosso símbolo. Portanto, a propriedade será protegida contra uso acidental ou substituição.

    Portanto, podemos “esconder” algo ocultamente em objetos que precisamos, mas outros não devem ver, usando propriedades simbólicas.

  2. Existem muitos símbolos do sistema usados ​​pelo JavaScript que são acessíveis como Symbol.*. Podemos usá-los para alterar alguns comportamentos internos. Por exemplo, ...... Symbol.iteratorpara iterables, Symbol.toPrimitivepara configurar a conversão de objeto em primitiva e assim por diante.

Fonte

snr
fonte