¡Esta es una revisión vieja del documento!
Tabla de Contenidos
Símbolos
Módulo perteneciente al curso Programación avanzada con JavaScript y ECMAScript.
Introducción
Los símbolos son un elemento nuevo en ECMAScript que ofrece la posibilidad de crear valores únicos. Los símbolos son objetos de un tipo básico nuevo llamado symbol. Esos objetos son inmutables (el valor asociado a un símbolo no puede cambiarse después de su creación) y únicos (no hay dos símbolos con el mismo valor).
Creación de símbolos
Los símbolos se crean usando la función Symbol:
var s1 = Symbol("foo");
A la función Symbol se le pasa una cadena (no se admite llamar a Symbol sin parámetros) que sirve como identificador del símbolo. Pero aunque le pasemos la misma cadena a dos símbolos, cada uno de ellos es distinto para ECMAScript. Por ejemplo, el siguiente código crea dos símbolos distintos:
var s1 = Symbol("foo");
var s2 = Symbol("foo");
s1 === s2; // false
s1 == s2; // false
Tampoco hay conversión alguna entre el símbolo y la cadena que se haya usado para crearlo:
var s1 = Symbol("foo"); s1 === "foo"; // false s1 == "foo"; // false
A partir de la cadena que se haya usado como identificación del símbolo (foo en nuestros ejemplos) no hay manera de poder obtener el símbolo asociado a ella.
Fíjate en que la función Symbol no es una función constructora. Usar new Symbol genera un error.
Los símbolos son un tipo nuevo, es decir usar typeof sobre una variable de tipo símbolo no devolverá “object”, sino que devolverá “symbol”.
Los símbolos en general no se convierten de forma automática a ningún tipo, e intentar concatenar un símbolo con una cadena da error:
var s1 = Symbol("foo"); var str = s1 + "bar"; // TypeError
Un símbolo básicamente solo es igual a sí mismo o bien a un objeto creado con Object(s) siendo s el propio símbolo:
var s1 = Symbol("foo"); var obj = Object(s1); typeof(s1); // "symbol" typeof(obj); // "object" s1 == obj; // true s1 === obj; // false
Para convertir un símbolo en una cadena puede usarse String(s) (siendo s el símbolo), pero esto no devuelve la cadena que se haya usado como identificador, sino que devuelve una representación en formato cadena del símbolo:
var s1 = Symbol("foo"); var str = String(s1); //"Symbol(foo)" str == s1; // false str === s1; // false str == "foo"; // false str === "foo"; // false
Variables privadas
Una de las cosas que mucha gente achaca a JavaScript es la imposibilidad de declarar métodos o propiedades privadas dentro de un objeto. Es cierto que el lenguaje no tiene ninguna palabra clave para declarar métodos o propiedades privadas, pero eso no implica que no se puedan crear: solo significa que no es tan directo como en otros lenguajes. Sí que es cierto que la notación de objeto no permite hacerlo, por lo que el mecanismo siempre pasa por crear el objeto a través de una función (sea o no constructora).
Revealing module pattern
Este patrón se usa en JavaScript para crear un singleton, es decir un objeto del que existe una sola instancia. Por supuesto nada impide que este objeto sea una función (en JavaScript las funciones son un tipo particular de objetos) que permita crear otros objetos, por lo que puedes usar este patrón para exponer una factoría.
Factoría: Un patrón de orientación a objetos. Una factoría es un objeto (o una función) cuya funcionalidad es la de crear otros objetos. Se suele usar cuando la creación del objeto no es trivial y/o bien requiere personalizaciones posteriores a la creación del objeto pero necesarias antes de poder usarlo.
El revealing module pattern consiste en una función anónima autoejecutable que termina devolviendo un objeto. Este objeto devuelto es el que actúa como singleton. Para “exponer” el singleton y que sea accesible se puede: o bien asignar a una variable global, o bien guardarlo dentro de algún contexto (usualmente el global). Todo lo que se declara dentro de la función anónima autoejecutable es privado y solo lo que se devuelve al final es accesible:
var Modulo = (function() { var p1 = function() { console.log("En p1"); p2();} var p2 = function() { console.log("En p2");} return { pub1: p1 }; })(); Modulo.p1(); // Error p1 is not a function Modulo.p2(); // Error p2 is not a funcion Modulo.pub1(); // Ejecuta pub1 (que es realmente p1)
Observa como todas las variables declaradas dentro de la función anónima no son accesibles desde fuera: las funciones p1 y p2 no se pueden llamar desde el exterior. Solo se puede acceder al objeto que se devuelve en el return. Si queremos hacer que alguna función declarada en el módulo, sea accesible debemos devolverla como una propiedad del objeto (es el caso de pub que simplemente nos permite acceder a la propia función privada p1).
El revealing module pattern se basa simplemente en el hecho de que todas las variables locales no son accesibles desde el exterior: p1 y p2 son variables locales de la función anónima autoejecutable.
Observa que la variable Modulo no contiene la función anónima, sino el resultado de invocar a la función anónima (es decir el objeto que devolvemos). Observa que la función la declaramos con:
(function() {...})();
Ese código no es (solo) la declaración de una función anónima: es la declaración de una función anónima y su invocación (observa los paréntesis finales). A esta construcción (declarar y ejecutar ipso-facto una función anónima) la llamamos “función anónima autoejecutable”. Una vez ejecutada ya no se puede volver ejecutar, dado que la función era anónima y no la hemos guardado en ninguna variable.
El revealing module pattern es pues una de las opciones que podemos usar para tener realmente funciones y métodos privados. El hándicap es que realmente es un singleton (no podemos tener dos instancias de Modulo) Si queremos tener algo más parecido a una “clase” de la cual crear objetos y que tenga métodos o propiedades privadas debemos usar una función constructora.
Visibilidad privada en función constructora
Usando una función constructora, el tener variables o funciones privadas a nivel de objeto es trivial: basta con no asignarlas a this sino declararlas como locales a la función constructora. Dado que cada creación al objeto implica una llamada a la función constructora esas variables locales existen por cada objeto:
var Foo = function() { var data = 42; this.value = function() { if (arguments.length == 0) { return data; } else { data = arguments[0]; } } this.inc = function() {data++} } var f1 = new Foo(); f1.value(); // 42 var f2 = new Foo(); f1.value(100); f1.value(); // 100 f2.value(); // 42 f1.data; // undefined f2.data; // undefined
En este caso la variable data es una variable privada de cada objeto construido mediante la función constructora Foo. Solo lo que se asigna a this forma parte real del objeto y por lo tanto es accesible.
Igual te estás preguntando cómo es posible que la variable data no sea destruida al finalizar la ejecución de la función Foo. La pregunta tiene toda la lógica del mundo, ya que la variable data es una variable local y las variables locales son destruidas al finalizar la ejecución de la función. La razón por la que no es destruida es porque es utilizada (accedida) desde funciones que tienen más ámbito de vida (en este caso desde funciones que se asignan a this y por lo tanto su ámbito de vida es el del objeto). Cuando una función declarada dentro de otra función (p. ej. la función value declarada dentro de la función Foo) accede a una variable local de la función contenedora, decimos que la función contenida “captura la variable” y que ejerce de closure.
DEMO: Revealing Module Pattern
El Revealing Module Pattern es uno de los patrones que más se utilizan en JavaScript para tener variables y funciones de visibilidad privada. Básicamente, dicho patrón, consiste en una función anónima autoejecutable:
(function() { })();
Todo lo que definamos dentro de la anterior función, serán variables y funciones privadas.
var miModulo = (function() { var value = 42; var foo = function(i) { return i + value * bar(); } var bar = function() { return 10;} return { // El primer ''foo'' es el nombre público y el segundo es el privado foo: foo } })();
La función foo es privada, pero la exponemos al devolverla con el objeto en el último return.
Al ejecutar el código anterior:
miModulo; // -> Object { foo: foo() } miModulo.pFoo(10); // -> 62 // Comprobamos que no podemos acceder a variables y funciones privadas: miModulo.value; // -> undefined miModulo.bar(); // -> TypeError: miModulo.bar is not a function miModulo.foo(); // -> TypeError: miModulo.foo is not a function
