¿Cuál es la motivación para llevar Símbolos a ES6?

368

ACTUALIZACIÓN : Recientemente apareció un artículo brillante de Mozilla . Léelo si tienes curiosidad.

Como ya sabrán, planean incluir un nuevo tipo de Símbolo primitivo en ECMAScript 6 (sin mencionar algunas otras cosas locas). Siempre pensé que la :symbolnoción en Ruby es innecesaria; podríamos usar fácilmente cadenas simples, como lo hacemos en JavaScript. Y ahora deciden complicar las cosas en JS con eso.

No entiendo la motivación. ¿Podría alguien explicarme si realmente necesitamos símbolos en JavaScript?

Yanis
fuente
66
No sé cuán auténtica es esta explicación, pero es un comienzo: tc39wiki.calculist.org/es6/symbols .
Felix Kling
8
Los símbolos permiten tanto , que permiten identificadores únicos con alcance en los objetos. Por ejemplo, tener propiedades en objetos a los que solo se puede acceder en un lugar.
Benjamin Gruenbaum
55
No estoy seguro de eso, ya que puede usar Object.getOwnPropertySymbols (o)
Yanis
44
Es más singularidad en lugar de privacidad.
Qantas 94 Heavy
2
Que iban a tener una implementación de la clase más complicada con privatey publicpalabras clave de atributos de clase que decidieron deshacerse de implementación de la clase más simple. En lugar de this.x = xque se suponía que debías hacer public x = xy para variables privadas private y = y. Decidieron deshacerse de eso para una implementación de clase mucho más mínima. Symbol sería una solución alternativa necesaria para obtener propiedades privadas en la implementación mínima.
lyschoening

Respuestas:

224

La motivación original para introducir símbolos en Javascript era habilitar propiedades privadas .

Desafortunadamente, terminaron siendo severamente degradados. Ya no son privados, ya que puede encontrarlos a través de la reflexión, por ejemplo, usando Object.getOwnPropertySymbolso proxies.

Ahora se conocen como símbolos únicos y su único uso previsto es evitar conflictos de nombres entre propiedades. Por ejemplo, el propio ECMAScript ahora puede introducir ganchos de extensión a través de ciertos métodos que puede colocar en objetos (por ejemplo, para definir su protocolo de iteración) sin arriesgarlos a chocar con los nombres de usuario.

Es discutible si eso es lo suficientemente fuerte como una motivación para agregar símbolos al idioma.

Andreas Rossberg
fuente
93
La mayoría de los idiomas (todos los principales afaik) proporcionan algún mecanismo, generalmente de reflexión, para obtener acceso al privado de todos modos.
Esailija
19
@Esailija, no creo que sea cierto, en particular, ya que muchos idiomas no ofrecen reflexión en primer lugar. La fuga de estado privado a través de la reflexión (como, por ejemplo, en Java) debe considerarse un error, no una característica. Esto es especialmente cierto en las páginas web, donde tener un estado privado confiable puede ser relevante para la seguridad. Actualmente, la única forma de lograrlo en JS es a través de cierres, que pueden ser tediosos y costosos.
Andreas Rossberg
38
El mecanismo no tiene que ser reflejo: C ++, Java, C #, Ruby, Python, PHP, Objective-C permiten el acceso de una forma u otra si realmente se quiere. No se trata realmente de habilidad sino de comunicación.
Esailija
44
@plalx, ​​en la web, la encapsulación a veces también se trata de seguridad.
Andreas Rossberg
3
@RolandPihlakas, desafortunadamente, Object.getOwnPropertySymbolsno es la única fuga; la más difícil es la capacidad de usar servidores proxy para interceptar el acceso a una propiedad "privada".
Andreas Rossberg,
95

Los símbolos no garantizan la verdadera privacidad, pero pueden usarse para separar las propiedades públicas e internas de los objetos. Tomemos un ejemplo donde podemos usar Symbolpara tener propiedades privadas.

Tomemos un ejemplo donde una propiedad de un objeto no es 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

Arriba, la Petpropiedad de la clase typeno es privada. Para hacerlo privado, tenemos que crear un cierre. El siguiente ejemplo ilustra cómo podemos hacer typeprivado el uso de un cierre.

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

Desventaja del enfoque anterior: Estamos introduciendo un cierre adicional para cada Petinstancia creada, lo que puede dañar el rendimiento.

Ahora te presentamos Symbol. Esto puede ayudarnos a hacer que una propiedad sea privada sin usar cierres innecesarios adicionales. Ejemplo de código a continuación:

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
fuente
15
¡Observe que las propiedades de los símbolos no son privadas ! Los símbolos no tienen colisión . Es posible que desee leer la respuesta aceptada.
Bergi
3
Sí, el símbolo no garantiza la verdadera privacidad, pero puede usarse para separar las propiedades públicas e internas de los objetos. Lo siento, olvidé agregar este punto a mi respuesta. Actualizaré mi respuesta en consecuencia.
Samar Panda
@SamarPanda, también podría decir que agregar prefijos a los miembros _no garantiza una verdadera privacidad, pero puede usarse para separar las propiedades públicas e internas de los objetos. En otras palabras, respuesta inútil.
Pacerier
10
No diría que no tiene sentido, ya que los símbolos no son enumerables por defecto, tampoco se puede acceder por 'error', mientras que cualquier otra tecla sí.
Patrick
55
Creo que su respuesta es la única que realmente tiene un ejemplo que tiene sentido, sobre por qué querría definir el atributo privado del objeto como un Símbolo, en lugar de solo un atributo normal.
Luis Lobo Borobia
42

Symbolsson un tipo de objeto nuevo y especial que se puede usar como un nombre de propiedad único en los objetos. Usar en Symbollugar de string's permite que diferentes módulos creen propiedades que no entren en conflicto entre sí. Symbolstambién se puede hacer privado, de modo que cualquiera que aún no tenga acceso directo a la propiedad no pueda acceder a sus propiedades Symbol.

SymbolsSon una nueva primitiva . Al igual que el number, stringy booleanprimitivas, Symboltienen una función que puede ser utilizado para crearlos. A diferencia de las otras primitivas, Symbolsno tienen una sintaxis literal (por ejemplo, cómo stringtiene '') - la única manera de crear ellos es el Symbolconstructor de la siguiente manera:

let symbol = Symbol();

En realidad, Symbolson solo una forma ligeramente diferente de adjuntar propiedades a un objeto: podría proporcionar fácilmente los Symbolsmétodos conocidos como estándar, tal como Object.prototype.hasOwnPropertyaparece en todo lo que hereda Object.

Estos son algunos de los beneficios del Symboltipo primitivo.

Symbols tener depurabilidad incorporada

Symbols se puede dar una descripción, que en realidad solo se usa para la depuración para hacer la vida un poco más fácil al iniciar sesión en una consola.

Symbolspuede usarse como Objectllaves

Aquí es donde se Symbolpone realmente interesante. Están fuertemente entrelazados con objetos. Symbolpuede asignarse como claves a objetos, lo que significa que puede asignar un número ilimitado de objetos únicos Symbola un objeto y garantizar que nunca entren en conflicto con las stringteclas u otros objetos únicos Symbols.

Symbols se puede usar como un valor único.

Supongamos que usted tiene una biblioteca de registro, que incluye múltiples niveles de registro, tales como logger.levels.DEBUG, logger.levels.INFO, logger.levels.WARNy así sucesivamente. En el código ES5 le gustaría hacer estos strings (so logger.levels.DEBUG === 'debug') o numbers ( logger.levels.DEBUG === 10). Ambos no son ideales ya que esos valores no son valores únicos, ¡pero sí lo Symbolson! Entonces logger.levelssimplemente se convierte en:

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

Lea más en este gran artículo .

Mihai Alexandru-Ionut
fuente
10
No estoy seguro de entender tu ejemplo, y por qué lo necesitarías log.levels = {DEBUG: Symbol('debug')y no simplemente log.levels = {DEBUG:'debug'}. Al final es lo mismo. Creo que vale la pena mencionar que los símbolos son invisibles al iterar sobre las teclas de un objeto. esa es su "cosa"
vsync
Un beneficio es que alguien no puede usar un literal accidentalmente y cree que funcionaría para siempre. (Tenga en cuenta que este no es un argumento realmente fuerte, ya que uno simplemente puede usar {}y lograr el mismo resultado (como valor único), o tal vez se prefiere un literal en ese proyecto, o puede decir que uno necesita leer el documento primero.) I personalmente creo que proporciona una buena legibilidad de significado único en el código
apple apple
tenga en cuenta que cuando se usa como valor único, el objeto literal también tiene una capacidad de depuración incorporada, es decir , se Symbol("some message")convierte en un {message:'some message'}objeto posiblemente mejor aquí, ya que puede agregar múltiples campos.
manzana manzana
38

Esta publicación trata sobre el Symbol(), suministrado con ejemplos reales que pude encontrar / hacer y hechos y definiciones que pude encontrar.

TLDR;

El Symbol()es el tipo de datos, introducido con el lanzamiento de ECMAScript 6 (ES6).

Hay dos hechos curiosos sobre el símbolo.

  • el primer tipo de datos y solo el tipo de datos en JavaScript que no tiene literal

  • cualquier variable, definida con Symbol(), obtiene contenido único, pero no es realmente privado .

  • cualquier dato tiene su propio Símbolo, y para los mismos datos los Símbolos serían los mismos . Más información en el siguiente párrafo, de lo contrario no es un TLRD; :)

¿Cómo inicializo el símbolo?

1. Para obtener un identificador único con un valor depurable

Puedes hacerlo de esta manera:

var mySymbol1 = Symbol();

O de esta manera:

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

La "some text here"cadena no se puede extraer del símbolo, es solo una descripción para fines de depuración. No cambia el comportamiento del símbolo de ninguna manera. Aunque, podría console.loghacerlo (lo cual es justo, ya que el valor es para la depuración, para no confundir ese registro con alguna otra entrada de registro):

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

2. Para obtener un símbolo para algunos datos de cadena

En este caso, el valor del símbolo se tiene realmente en cuenta y de esta manera dos símbolos pueden no ser únicos.

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

Llamemos a esos símbolos símbolos de "segundo tipo". No se cruzan con los símbolos de "primer tipo" (es decir, los definidos con Symbol(data)) de ninguna manera.

Los siguientes dos párrafos pertenecen solo al símbolo de primer tipo .

¿Cómo me beneficio al usar Symbol en lugar de los tipos de datos más antiguos?

Primero consideremos un objeto, un tipo de datos estándar. Podríamos definir algunos pares clave-valor allí y tener acceso a los valores especificando la clave.

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

¿Qué pasa si tenemos dos personas con el nombre de Peter?

Haciendo esto:

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

No tendría mucho sentido.

Entonces, parece ser un problema de dos personas absolutamente diferentes que tienen el mismo nombre. Entonces veamos nuevo Symbol(). Es como una persona en la vida real: cualquier persona es única , pero sus nombres pueden ser iguales. Definamos dos "personas".

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

Ahora tenemos dos personas diferentes con el mismo nombre. ¿Son nuestras personas realmente diferentes? Son; puedes verificar esto:

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

¿Cómo nos beneficiamos allí?

Podemos hacer dos entradas en su objeto para las diferentes personas y no pueden confundirse de ninguna manera.

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

Nota:
Sin embargo, vale la pena notar que al encadenar el objeto JSON.stringifyse eliminarán todos los pares inicializados con un Símbolo como clave.
La ejecución Object.keystampoco devolverá tales Symbol()->valuepares.

Usando esta inicialización, es absolutamente imposible confundir las entradas para la primera y la segunda persona. Llamar console.loga ellos mostrará correctamente sus segundos nombres.

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

Cuando se usa en un objeto, ¿cómo es diferente en comparación con la definición de propiedad no enumerable?

De hecho, ya existía una forma de definir una propiedad para ocultar Object.keysy enumerar. Aquí está:

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

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

¿Qué diferencia Symbol()trae allí? La diferencia es que aún puede obtener la propiedad definida Object.definePropertyde la manera habitual:

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

Y si se define con Symbol como en el párrafo anterior:

fruit = Symbol("apple");

Tendrá la capacidad de recibir su valor solo si conoce su variable, es decir

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

Además, definir otra propiedad bajo la clave "apple"hará que el objeto descarte la anterior (y si está codificada, podría arrojar un error). Entonces, ¡no más manzanas! Eso es una lástima. Al referirse al párrafo anterior, los Símbolos son únicos y definen una clave que Symbol()lo hará único.

Conversión de tipo y verificación

  • A diferencia de otros tipos de datos, es imposible convertirlos Symbol()a cualquier otro tipo de datos.

  • Es posible "hacer" un símbolo basado en un tipo de datos primitivo llamando Symbol(data).

  • En términos de verificar el tipo, nada cambia.

    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
fuente
¿Se migró esto de la documentación SO?
Knu
1
@KNU no lo fue;
Recopilé
Realmente hermosa respuesta!
Mihai Alexandru-Ionut
1
Gran respuesta en Symbol, sin embargo, todavía no sé por qué usaría objeto con teclas de símbolo en lugar de una matriz. Si tengo varias personas como {"peter": "pan"} {"john": "doe"} me siento mal si las coloco en un solo objeto. Por la misma razón que no hago clases con propiedades duplicadas como personFirstName1, personFirstName2. Esto, combinado con la incapacidad de stringificarlo, no veo beneficios, solo desventajas.
eldo
18

Así es como lo veo. Los símbolos proporcionan 'un nivel adicional de privacidad' al evitar que las claves / propiedades de un objeto se expongan a través de algunos métodos populares como Object.keys () y 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…)

Aunque dado un objeto per se, tales propiedades aún pueden exponerse a través de la reflexión, el proxy, Object.getOwnPropertySymbols (), etc., no hay medios naturales para acceder a ellos a través de algunos métodos directos, que a veces pueden ser suficientes desde una perspectiva OOP.

Chong Lip Phang
fuente
2

Un símbolo JS es un nuevo tipo de datos primitivos. Son tokens que sirven como identificaciones únicas . Se puede crear un símbolo usando el Symbolconstructor. Tomemos, por ejemplo, este fragmento de 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.

A menudo es útil usar símbolos como claves de propiedad de objeto únicas, por ejemplo:

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
fuente
0

Los símbolos tienen dos casos de uso principales:

  1. Propiedades de objetos "ocultos". Si queremos agregar una propiedad a un objeto que "pertenece" a otro script o biblioteca, podemos crear un símbolo y usarlo como una clave de propiedad. No aparece una propiedad simbólica for..in, por lo que no se procesará accidentalmente junto con otras propiedades. Tampoco se accederá directamente, porque otro script no tiene nuestro símbolo. Por lo tanto, la propiedad estará protegida contra el uso accidental o la sobrescritura.

    Por lo tanto, podemos ocultar "encubiertamente" algo en los objetos que necesitamos, pero otros no deberían ver, usando propiedades simbólicas.

  2. Hay muchos símbolos del sistema utilizados por JavaScript a los que se puede acceder como Symbol.*. Podemos usarlos para alterar algunos comportamientos incorporados. Por ejemplo, ...... Symbol.iteratorpara iterables, Symbol.toPrimitivepara configurar la conversión de objeto a primitivo, etc.

Fuente

snr
fuente