ES6 en Detalle: Símbolos

20 octubre, 2015 5:35 por

Esta es una traducción del artículo original publicado en el blog de Mozilla Hacks.

¿Qué son los símbolos ES6?

Los símbolos no son logos.

No son pequeñas figuras que puedes usar en tu código.

let 😻 = 😺 × 😍;  // SyntaxError

No son son un recurso literario que representa otra cosa.

Así que, ¿qué son los símbolos?

El séptimo tipo

Desde que se estandarizó JavaScript en 1997, han habido siete tipos. Hasta el ES6, cada valor en un programa JavaScript caía en una de esas categorías.

  • undefined
  • null
  • Boolean
  • Number
  • String
  • Object

Cada tipo es un conjunto de valores. Los primeros cinco conjuntos son todos finitos.  Claro, solo dos valores booleanos, true y false, y no van a crear nuevos valores. Hay muchos más valores para los números y las cadenas de caracteres. El estándar dice que hay 18,437,736,874,454,810,627 números diferentes, (incluyendo NaN, el número cuyo nombre es la versión corta de Not a Number (“No es un número”)). Eso no es nada comparado con el numero de diferentes posibilidades de cadenas de caracteres, el cual creo que es (2144,115,188,075,855,872 − 1) ÷ 65,535 … aunque es posible que haya contado mal.

El conjunto de valores para Object, sin embargo, es ilimitado. Cada objeto es único, un precioso copo de nieve. Cada vez que tu abres una página web, un montón de nuevos objetos son creados.

Los símbolos ES6 son valores, pero no son cadenas de caracteres, no son objetos. Son algo nuevo: el séptimo tipo de valores.

Vamos a hablar de un escenario donde ellos podrían sernos útiles.

Un pequeño y simple booleano

A veces sería muy conveniente guardar datos extras en un objeto JavaScript que le pertenece a alguien más.

Por ejemplo, supongamos que estamos escribiendo una librería JavaScript que usa transiciones CSS para hacer elementos DOM moverse por la pantalla. Te habrás dado cuenta de si aplicar múltiples transiciones CSS a un solo div al mismo tiempo no funciona. Causa feos y discontinuos “saltos”. Crees que puedes arreglar esto, pero primero necesitas una manera de descubrir si un elemento dado ya se está moviendo.

¿Cómo puedes resolver esto?

Una manera es usar APIs CSS para preguntarle al navegador si el elemento se está moviendo. Pero eso suena como excesivo. Tu librería ya debería saber si un elemento se está moviendo, ¡es el código que hace que se moviera en primer lugar!

Lo que realmente quieres, es un método de no perder de vista cuáles elementos se están moviendo. Puedes mantener en un arreglo todos los elementos en movimiento. Cada vez que tu librería se llame para mover un elemento, puedes buscar en el arreglo para ver si el elemento ya esta ahí.

Hmm. Una búsqueda linear sería lenta si el arreglo es grande.

Lo que realmente quieres hacer es solamente colocar una bandera en el elemento:

if (element.isMoving) {
  smoothAnimations(element);
}
element.isMoving = true;

Aquí también hay problemas potenciales. Todos relacionados al hecho que tu código no es el único usando el DOM.

  1. Otro código usando el for-in o Object.keys() podría tropezarse con la propiedad que creaste.
  2. Algún otro desarrollador astuto pudo haber creado una librería con la misma técnica antes, y tu librería no se llevaría bien con la librería existente.
  3. Algún otro desarrollador astuto podría pensar hacer esto en el futuro, y tu librería no interactuaría bien con la futura librería.
  4. El comité de estándares podría decidir agregar un método llamado .isMoving() a todos los elementos. ¡Entonces estás frito!

Por supuesto, puedes obviar los últimos tres problemas eligiendo un nombre tan tedioso o ridículo que nadie en el mundo lo llamaría de esa manera:

if (element.__$jorendorff_animation_library$POR_FAVOR_NO_USAR$isMoving__) {
  smoothAnimations(element);
}
element.__$jorendorff_animation_library$POR_FAVOR_NO_USAR$isMoving__ = true;

Esto no parece valer la pena.

Puedes generar un nombre prácticamente único para la propiedad usando criptografía:

// obtener 1024 caracteres Unicode de ruido
var isMoving = SecureRandom.generateName();

...

if (element[isMoving]) {
  smoothAnimations(element);
}
element[isMoving] = true;

La sintaxis object[name] te deja usar literalmente cualquier cadena como nombre de propiedad. Así que esto podría funcionar: las colisiones son virtualmente imposibles, y tu código se ve bien.

Pero esto te llevará a una mala experiencia al depurar tu código. Cada vez que veas console.log() de un elemento con tu propiedad, vas a ver una enorme cadena de basura. ¿Y qué pasa si tú necesitas más de una propiedad como eésta? ¿Cómo te las arreglarías? Tendrían un nombre diferente cada vez que recargues.

¿Por quá esto es tan difícil? ¡Solo queremos un simple booleano!

Los símbolos son la respuesta

Los símbolos son valores que los programas crean y usan como llaves de propiedades, sin riesgo de colisión de nombres.

var mySymbol = Symbol();

Llamando Symbol() se crea un nuevo símbolo, un valor que no es igual a ningún otro valor.

Así como con las cadenas o números, puedes usar un símbolo como una llave de propiedad. Porque no es igual a ninguna cadena, esta propiedad con llave de símbolo está garantizada que no chocara con ninguna otra propiedad.

obj[mySymbol] = "ok!"; // garantizado que no chocará!
console.log(obj[mySymbol]); // ok!

Así es como podríamos usar símbolos en la situación que teníamos arriba:

// Creamos un símbolo único
var isMoving = Symbol("isMoving");

...

if (element[isMoving]) {
  smoothAnimations(element);
}
element[isMoving] = true;

Unas cuantas notas sobre este código:

  • La cadena “isMoving” en Symbol(“isMoving”) es llamada una descripción. Es de mucha ayuda a la hora de depurar el código. Se muestra cuando escribes el símbolo en console.log(), cuando lo conviertes en una cadena usando .toString(), y posiblemente en mensajes de error. Eso es todo.
  • element[isMoving] es llamada una propiedad con llave de símbolo. Es simplemente una propiedad cuyo nombre es un símbolo en lugar de una cadena. Aparte de eso, es en toda manera una propiedad normal.
  • Como los elementos de un arreglo, las propiedades con llave de símbolo no pueden obtenerse usando la sintaxis de punto, como en obj.name. Se deben obtener usando los [].
  • Es trivial acceder a una propiedad con llave de símbolo si ya tienes el símbolo. El ejemplo de arriba te muestra como puedes obtener y asignar element[isMoving], y podemos incluso preguntar if (isMoving in element), y también delete element[isMoving] si lo necesitásemos.
  • Por otro lado, todo esto es sólo posible dentro del rango de validez de isMoving. Esto hace a los símbolos un mecanismo de encapsulación débil: un módulo que crea unos pocos símbolos por sí mismo, puede usarlos en lo que sea que el objeto quiera, sin temor a chocar con propiedades creadas por otro código.

Porque las llaves de símbolo fueron diseñadas para evadir los choques, la mayoría de las características de inspección de objetos JS simplemente ignoran los símbolos. Un ciclo for-in, por instancia, solo itera sobre las llaves de hilera de un objeto. Las llaves de símbolo son saltadas. Object.keys(obj) y Object.getOwnPropertyNames(obj) hacen lo mismo. Pero los símbolos no son exactamente privados: es posible usar una nueva API, Object.getOwnPropertySymbols(obj) para listar las llaves de símbolo de un objeto. Otra API nueva, Reflect.ownKeys(obj), retornará ambos tipos de llave. (Discutiremos la API Reflect en otro artículo.)

La librerías y plataformas seguramente encontrarán muchos usos para los símbolos, y como veremos más adelante, el lenguaje por sí mismo los utiliza para muchos propósitos.

¿Pero, exactamente qué son los símbolos?

> typeof Symbol()
"symbol"

Los símbolos no son exactamente como ninguna otra cosa.

Son inmutables, una vez creados. No puedes ponerles propiedades (y si intentas eso en modo estricto, obtendrás un TypeError). Ellos pueden ser nombres de propiedades. Estas son cualidades de las hileras de caracteres.

Por otro lado, cada símbolo es único, distinto de los demás (incluso otros que tengan la misma descripción) y puedes fácilmente crear otros. Esas son cualidades como de los objetos.

Los símbolos ES6, son similares a los símbolos más tradicionales en lenguajes como Lisp y Ruby, pero no tan integrados al lenguaje. En Lisp todos los identificadores son símbolos. En JS, los identificadores y la mayoría de las llaves de propiedades aún se consideran cadenas. Los símbolos son solo una opción extra.

Una pequeña advertencia sobre los símbolos: a diferencia de todo lo demás en el lenguaje, no pueden automáticamente ser convertidos en cadenas. Intentar concatenar símbolos y cadenas resultará en un TypeError.

> var sym = Symbol("<3"); 
> "your symbol is " + sym
// TypeError: can't convert symbol to string
> `your symbol is ${sym}`
// TypeError: can't convert symbol to string

Puedes evitar esto explícitamente convirtiendo el símbolo a una cadena, escribiendo String(sym) o sym.toString().

Tres grupos de símbolos

Hay tres modos de obtener un símbolo.

  • Llamar Symbol(). Como ya discutimos, esto retorna un símbolo único cada vez que es llamado.
  • Llamar Symbol.for(string). Esto accede a un conjunot de símbolos existentes llamados registro de símbolos. A diferencia de los símbolos únicos de symbol(), los símbolos del registro son compartidos. Si llamas Symbol.for("gato") 30 veces, retornará el mismo símbolo cada vez. El registro es de mucha ayuda, cuando múltiples paginas o módulos en la misma pagina necesitan compartir un símbolo.
  • Usar símbolos como Symbol.iterator definidos por el estándar. Unos pocos símbolos son definidos por el estándar. Cada uno tiene su propio uso especial.

Si aún no estás seguro si los símbolos serán tan útiles, la ultima categoría es interesante, porque muestran qué tan útiles han probado ser en la práctica.

Cómo la especificación ES6 utiliza símbolos predefinidos

Ya hemos visto una manera de que los símbolos ES6 evaden conflictos con el código existente. Anteriormente, en el artículo sobre los iteradores vimos que el ciclo for (var item of myArray) comienza llamando myArray[Symbol.iterator](). Mencioné que éste método pudo haber sido llamado myArray.iterator(), pero un símbolo es mejor para la compatibilidad.

Ahora que sabemos lo que son los símbolos, es fácil comprender por qué se hizo de esta manera, y lo que significa.

Aquí hay algunos otros lugares donde se usan los símbolos ES6 (estas características aún no están implementadas en Firefox).

  • Hacer instanceof extensible.En el ES6, la expresión object instanceof constructor, es especificada como un método de un constructor: constructor[Symbol.hasInstance](object). Esto quiere decir es extensible.
  • Eliminar conflictos entra nuevas características y código viejo. Esto es verdaderamente oscuro, pero hemos encontrado que algunos métodos de arreglos ES6 rompieron algunas webs, solo por el hecho de estar ahí. Otros estándares web tuvieron problemas similares: simplemente agregar nuevos métodos en el navegador podría romper sitios existentes. Sin embargo, principalmente fue causado por algo llamado alcance dinámico, así el ES6 introduce un símbolo especial, Symbol.unscopables, para que los estándares web puedan prevenir que algunos métodos entren en un alcance dinámico.
  • Soportar nuevos tipos de string-matching. En el ES5, str.match(myObject) intenta convertir myObject a RegExp. En ES6, primero chequeamos si myObject tiene un método myObject[Symbol.match](str). Ahora las librerías pueden proveer clases de análisis de cadenas personalizadas, que funcionan en todos los lugares donde el objeto RegExp funciona.

Cara uno de esos usos son estrechos, es difícil de ver una de esas características tener por sí misma un gran impacto en mi código del día a día. A la larga es más interesante. Los símbolos bien predefinidos son versiones JS mejoradas de __doubleUnderscores en PHP y Python. El estándar los usará en el futuro para agregar nuevos ganchos en el lenguaje sin arriesgar el código existente.

¿Cuándo puedo usar los símbolos ES6?

Los símbolos fueron implementados en Firefox 36 y Chrome 38. Yo los implementé para Firefox, así que si tus símbolos se comportan de manera extraña, y sabes con quién hablar.

Para soportar los navegadores que aún no suportan nativamente los símbolos ES6, puedes usar una abstracción como core.js. Como los símbolos no son como nada más en el lenguaje, la abstracción no es perfecta. Lee las advertencias.

The following two tabs change content below.
Si, mi biografia esta vacia. 🙂

Compartir artículo:

Empezar la discusión en foro.mozilla-hispano.org

  • ¡Participa!

    Firefox Friends »
    Agrega botones de Firefox en tu sitio web y comparte tu amor por Mozilla Firefox.
    Ayuda a otros usuarios en Twitter.
    Colabora con la comunidad »
    En Mozilla lo importante son las personas. Descubre cómo puedes colaborar.

    Boletín Firefox

    Suscríbete al boletín de novedades de Firefox.

  • Descargas

    Descarga los programas de Mozilla.

    Lo más visto

    cc-by-sa