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

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

Como sabrá, están planeando incluir un nuevo tipo de primitivo Símbolo en ECMAScript 6 (por no mencionar algunas otras cosas locas). Siempre pensé que la noción de :symbol en Ruby es innecesaria; podríamos usar cadenas sencillas fácilmente, como 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?

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

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

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

Si eso es lo suficientemente fuerte, una motivación para agregar símbolos al lenguaje es discutible.

Los símbolos no garantizan la verdadera privacidad, pero se pueden usar para separar las propiedades públicas y las internas de los objetos. Tomemos un ejemplo donde podemos usar Symbol para 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, el type propiedad de clase Pet no es privado. Para hacerlo privado, tenemos que crear un cierre. El siguiente ejemplo ilustra cómo podemos hacer que el type privado usando 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 instancia de Pet creada, que puede dañar el rendimiento.

Ahora presentamos Symbol . Esto puede ayudarnos a hacer que una propiedad sea privada sin usar cierres adicionales innecesarios. 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 

Symbols son un tipo de objeto nuevo y especial que se puede usar como un nombre de propiedad único en los objetos. Usar Symbol s en lugar de string s permite que diferentes módulos creen propiedades que no entren en conflicto entre sí. Symbols también pueden hacerse privados, de modo que ninguna persona que no tenga acceso directo al Symbol pueda acceder a sus propiedades.

Symbols son una nueva primitiva . Al igual que las primitivas de number , string y boolean , los Symbol tienen una función que se puede usar para crearlos. A diferencia de las otras primitivas, los Symbols no tienen una syntax literal (por ejemplo, cómo la string s tiene '' ) – la única forma de crearlos es con el constructor de Symbol de la siguiente manera:

 let symbol = Symbol(); 

En realidad, los Symbol s son solo una forma ligeramente diferente de adjuntar propiedades a un objeto: podría proporcionar fácilmente los Symbol conocidos como métodos estándar, al igual que Object.prototype.hasOwnProperty que aparece en todo lo que hereda de Object .

Estos son algunos de los beneficios del tipo primitivo Symbol .

Symbols tienen depuración incorporada

Symbols se les puede dar una descripción, que en realidad solo se usa para la depuración para facilitar la vida al iniciar sesión en una consola.

Symbols se pueden usar como claves de Object

Aquí es donde Symbol vuelve realmente interesante. Están fuertemente entrelazados con objetos. Symbol s se pueden asignar como claves a los objetos, lo que significa que se puede asignar un número ilimitado de Symbol exclusivos a un objeto y se garantiza que estos nunca entren en conflicto con string teclas de string u otros Symbol únicos.

Symbols se pueden usar como un valor único.

Supongamos que tiene una biblioteca de registro, que incluye varios niveles de registro, como logger.levels.DEBUG , logger.levels.INFO , logger.levels.WARN , y así sucesivamente. En el código ES5, le gustaría hacer estas string s (por logger.levels.DEBUG === 'debug' tanto, logger.levels.DEBUG === 'debug' ), o number s ( logger.levels.DEBUG === 10 ). Ambos no son ideales ya que esos valores no son valores únicos, ¡pero los Symbol son! Entonces logger.levels simplemente 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.

Así es como lo veo. Los símbolos proporcionan “un nivel extra 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 se les haya dado un objeto per se, tales propiedades aún se pueden exponer a través de reflection, proxy, Object.getOwnPropertySymbols () etc., no hay medios naturales para acceder a ellas mediante unos pocos métodos directos, que a veces pueden ser suficientes desde la perspectiva de OOP.

Esta publicación trata sobre el Symbol() , que se proporciona 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 exclusivo, pero no es realmente privada .

  • 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 forma:

 var mySymbol1 = Symbol(); 

O de esta manera:

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

La cadena "some text here" no se puede extraer del símbolo, solo es una descripción para fines de depuración. No cambia el comportamiento del símbolo de ninguna manera. Aunque, podría console.log it (que 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 ser no ú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 del primer tipo .

¿Cómo me beneficio utilizando Symbol en lugar de los tipos de datos anteriores?

Consideremos primero 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 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 un mismo nombre. Vamos a referirnos a un nuevo Symbol() . Es como una persona en la vida real: cualquier persona es única , pero sus nombres pueden ser iguales. Vamos a definir dos “personas”.

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

Ahora tenemos dos personas diferentes con el mismo nombre. ¿Son nuestras personas diferentes en verdad? 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 se pueden confundir de ninguna manera.

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

Nota:
Sin embargo, vale la pena observar que el stringifying del objeto con JSON.stringify todos los pares inicializados con un símbolo como clave.
La ejecución de Object.keys tampoco devolverá tales pares de Symbol()->value .

Usando esta inicialización, es absolutamente imposible confundir las entradas para la primera y segunda persona. Llamar a console.log para ellos dará como resultado 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 manera de definir una propiedad que se oculta a Object.keys y enumeración. Aquí está:

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

¿Qué diferencia trae Symbol() allí? La diferencia es que aún puede obtener la propiedad definida con Object.defineProperty de 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, la definición de otra propiedad debajo de la clave "apple" hará que el objeto suelte el más antiguo (y si está codificado, podría arrojar un error). Entonces, ¡no más manzanas! Eso es una lástima. En referencia al párrafo anterior, los símbolos son únicos y la definición de una clave como Symbol() lo hará único.

Tipo de conversión y verificación

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

  • Es posible “hacer” un símbolo basado en el tipo de datos primitivo llamando a 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