Por especificación, sólo dos tipos primitivos pueden servir como claves de propiedad de objeto:
tipo de cadena, o
tipo de símbolo.
De lo contrario, si se utiliza otro tipo, como número, se convierte automáticamente en cadena. Entonces obj[1]
es lo mismo que obj["1"]
, y obj[true]
es lo mismo que obj["true"]
.
Hasta ahora hemos estado usando sólo cadenas.
Ahora exploremos los símbolos, veamos qué pueden hacer por nosotros.
Un "símbolo" representa un identificador único.
Se puede crear un valor de este tipo usando Symbol()
:
let id = Símbolo();
Tras la creación, podemos dar a los símbolos una descripción (también llamada nombre de símbolo), lo que resulta especialmente útil para fines de depuración:
// id es un símbolo con la descripción "id" let id = Símbolo("id");
Se garantiza que los símbolos serán únicos. Incluso si creamos muchos símbolos con exactamente la misma descripción, son valores diferentes. La descripción es sólo una etiqueta que no afecta en nada.
Por ejemplo, aquí hay dos símbolos con la misma descripción pero no son iguales:
let id1 = Símbolo("id"); let id2 = Símbolo("id"); alerta(id1 == id2); // FALSO
Si está familiarizado con Ruby u otro lenguaje que también tenga algún tipo de “símbolos”, no se equivoque. Los símbolos de JavaScript son diferentes.
Entonces, para resumir, un símbolo es un "valor único primitivo" con una descripción opcional. Veamos dónde podemos usarlos.
Los símbolos no se convierten automáticamente en una cadena
La mayoría de los valores en JavaScript admiten la conversión implícita a una cadena. Por ejemplo, podemos alert
de casi cualquier valor y funcionará. Los símbolos son especiales. No se convierten automáticamente.
Por ejemplo, esta alert
mostrará un error:
let id = Símbolo("id"); alerta(identificación); // TypeError: no se puede convertir un valor de símbolo en una cadena
Esto es una “protección del lenguaje” contra errores, porque las cadenas y los símbolos son fundamentalmente diferentes y no deberían convertirse accidentalmente uno en otro.
Si realmente queremos mostrar un símbolo, debemos llamar explícitamente .toString()
en él, como aquí:
let id = Símbolo("id"); alerta(id.toString()); // Símbolo(id), ahora funciona
O obtenga la propiedad symbol.description
para mostrar solo la descripción:
let id = Símbolo("id"); alerta(id.descripción); // identificación
Los símbolos nos permiten crear propiedades "ocultas" de un objeto, a las que ninguna otra parte del código puede acceder o sobrescribir accidentalmente.
Por ejemplo, si estamos trabajando con objetos user
, que pertenecen a un código de terceros. Nos gustaría agregarles identificadores.
Usemos una clave de símbolo para ello:
let usuario = { // pertenece a otro código nombre: "Juan" }; let id = Símbolo("id"); usuario[id] = 1; alerta (usuario [id]); // podemos acceder a los datos usando el símbolo como clave
¿Cuál es el beneficio de utilizar Symbol("id")
sobre una cadena "id"
?
Como los objetos user
pertenecen a otra base de código, no es seguro agregarles campos, ya que podríamos afectar el comportamiento predefinido en esa otra base de código. Sin embargo, no se puede acceder a los símbolos accidentalmente. El código de terceros no tendrá en cuenta los símbolos recién definidos, por lo que es seguro agregar símbolos a los objetos user
.
Además, imagine que otro script quiere tener su propio identificador dentro de user
, para sus propios fines.
Entonces ese script puede crear su propio Symbol("id")
, como este:
//... let id = Símbolo("id"); usuario[id] = "Su valor de identificación";
No habrá conflicto entre nuestros identificadores y los suyos, porque los símbolos siempre son diferentes, incluso si tienen el mismo nombre.
…Pero si usáramos una cadena "id"
en lugar de un símbolo para el mismo propósito, entonces habría un conflicto:
dejar usuario = { nombre: "Juan" }; // Nuestro script usa la propiedad "id" user.id = "Nuestro valor de identificación"; // ...Otro script también quiere "id" para sus propósitos... user.id = "Su valor de identificación" // ¡Auge! sobrescrito por otro script!
Si queremos utilizar un símbolo en un objeto literal {...}
, necesitamos corchetes alrededor.
Como esto:
let id = Símbolo("id"); dejar usuario = { nombre: "Juan", [identificación]: 123 // no "identificación": 123 };
Esto se debe a que necesitamos el valor de la variable id
como clave, no la cadena "id".
Las propiedades simbólicas no participan en el bucle for..in
.
Por ejemplo:
let id = Símbolo("id"); dejar usuario = { nombre: "Juan", edad: 30, [identificación]: 123 }; para (dejar ingresar usuario) alerta (clave); // nombre, edad (sin símbolos) // el acceso directo por el símbolo funciona alerta( "Directo: " + usuario[id] ); // Directo: 123
Object.keys(user) también los ignora. Eso es parte del principio general de “ocultar propiedades simbólicas”. Si otro script o una biblioteca recorre nuestro objeto, no accederá inesperadamente a una propiedad simbólica.
Por el contrario, Object.assign copia propiedades de cadena y símbolo:
let id = Símbolo("id"); dejar usuario = { [identificación]: 123 }; let clone = Object.assign({}, usuario); alerta (clon [id]); // 123
No hay ninguna paradoja aquí. Eso es por diseño. La idea es que cuando clonamos un objeto o fusionamos objetos, normalmente queremos que se copien todas las propiedades (incluidos símbolos como id
).
Como hemos visto, normalmente todos los símbolos son diferentes, aunque tengan el mismo nombre. Pero a veces queremos que los símbolos con el mismo nombre sean las mismas entidades. Por ejemplo, diferentes partes de nuestra aplicación quieren acceder al símbolo "id"
que significa exactamente la misma propiedad.
Para lograrlo, existe un registro global de símbolos . Podemos crear símbolos en él y acceder a ellos más tarde, y garantiza que los accesos repetidos con el mismo nombre devuelvan exactamente el mismo símbolo.
Para leer (crear si está ausente) un símbolo del registro, use Symbol.for(key)
.
Esa llamada verifica el registro global y, si hay un símbolo descrito como key
, lo devuelve; de lo contrario, crea un nuevo Symbol(key)
y lo almacena en el registro mediante la key
dada.
Por ejemplo:
// leer del registro global let id = Símbolo.for("id"); // si el símbolo no existía se crea // leerlo de nuevo (tal vez de otra parte del código) let idOtra vez = Symbol.for("id"); // el mismo símbolo alerta( id === idOtra vez ); // verdadero
Los símbolos dentro del registro se denominan símbolos globales . Si queremos un símbolo para toda la aplicación, accesible en cualquier parte del código, para eso están.
Eso suena a Rubí
En algunos lenguajes de programación, como Ruby, hay un único símbolo por nombre.
En JavaScript, como podemos ver, eso es cierto para los símbolos globales.
Hemos visto que para símbolos globales, Symbol.for(key)
devuelve un símbolo por nombre. Para hacer lo contrario – devolver un nombre por símbolo global – podemos usar: Symbol.keyFor(sym)
:
Por ejemplo:
//obtiene el símbolo por nombre let sym = Símbolo.for("nombre"); let sym2 = Símbolo.for("id"); //obtener nombre por símbolo alerta( Símbolo.keyFor(sym) ); // nombre alerta (Símbolo.keyFor (sym2)); // identificación
Symbol.keyFor
utiliza internamente el registro de símbolos global para buscar la clave del símbolo. Entonces no funciona para símbolos no globales. Si el símbolo no es global, no podrá encontrarlo y devuelve undefined
.
Dicho esto, todos los símbolos tienen la propiedad description
.
Por ejemplo:
let globalSymbol = Symbol.for("nombre"); let localSymbol = Símbolo("nombre"); alerta (Símbolo.keyFor (globalSymbol)); // nombre, símbolo global alerta (Símbolo.keyFor (localSymbol)); // indefinido, no global alerta (símbolo local. descripción); // nombre
Existen muchos símbolos de "sistema" que JavaScript usa internamente y podemos usarlos para ajustar varios aspectos de nuestros objetos.
Se enumeran en la especificación de la tabla de símbolos conocidos:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
…etcétera.
Por ejemplo, Symbol.toPrimitive
nos permite describir la conversión de objeto a primitivo. Veremos su uso muy pronto.
Otros símbolos también nos resultarán familiares cuando estudiemos las características correspondientes del lenguaje.
Symbol
es un tipo primitivo para identificadores únicos.
Los símbolos se crean con la llamada Symbol()
con una descripción opcional (nombre).
Los símbolos siempre tienen valores diferentes, incluso si tienen el mismo nombre. Si queremos que los símbolos con el mismo nombre sean iguales, entonces deberíamos usar el registro global: Symbol.for(key)
devuelve (crea si es necesario) un símbolo global con key
como nombre. Varias llamadas de Symbol.for
con la misma key
devuelven exactamente el mismo símbolo.
Los símbolos tienen dos casos de uso principales:
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 clave de propiedad. Una propiedad simbólica no aparece en for..in
, por lo que no se procesará accidentalmente junto con otras propiedades. Además, no se accederá directamente a él 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 "de forma encubierta" algo en objetos que necesitamos, pero que otros no deberían ver, utilizando propiedades simbólicas.
Hay muchos símbolos del sistema utilizados por JavaScript a los que se puede acceder como Symbol.*
. Podemos usarlos para alterar algunos comportamientos integrados. Por ejemplo, más adelante en el tutorial usaremos Symbol.iterator
para iterables, Symbol.toPrimitive
para configurar la conversión de objeto a primitivo, etc.
Técnicamente, los símbolos no están 100% ocultos. Hay un método incorporado Object.getOwnPropertySymbols(obj) que nos permite obtener todos los símbolos. También hay un método llamado Reflect.ownKeys(obj) que devuelve todas las claves de un objeto, incluidas las simbólicas. Pero la mayoría de las bibliotecas, funciones integradas y construcciones de sintaxis no utilizan estos métodos.