Variables privadas en clases ES2015

Una de las novedades de ES6 es la posibilidad de usar clases. Si bien esas clases son realmente sugar syntax sobre el patrón de función constructora, tienen alguna característica que simplifica el uso de dicho patrón y ofrecen una sintaxis que según como se mire puede ser más cómoda. Pero el estándard de ES2015 no tiene ningún mecanismo para indicar que una variable de una clase sea pública o privada. La visibilidad de variables en JavaScript es un clásico, sobre el que ya he escrito algún post. Pero las técnicas que usábamos hasta ahora para simular la visibilidad privada no aplican cuando usamos la sintaxis de clases… y debemos usar mecanismos alternativos. En este post veremos dos de ellos.

Supongamos que tenemos una clase ES2015 como la siguiente:

  1. class Foo {
  2.     constructor(name) {
  3.         this._name = name;
  4.     }
  5.  
  6.     get name() {
  7.         return _name;
  8.     }
  9. }

El objetivo es que _name sea privado y solo pueda accederse a su valor a través de la propiedad name.

Símbolos al rescate

La primera solución implica el uso de un nuevo concepto de ES2015: los símbolos. Un símbolo permite definir un nombre único desconocido y que tan solo puede ser obtenido a partir de la referencia al símbolo original que ha creado dicho nombre.  El siguiente código explica el uso de símbolos:

  1. var sfoo = Symbol("foo");
  2. var a = {};
  3. a[sfoo] = "sfoo";
  4. console.log(a.sfoo); // undefined
  5. console.log(a[sfoo]);   // sfoo
  6. console.log(a[Symbol("foo")])    // undefined

Podemos asignar una propiedad de un objeto a un símbolo, y entonces requerimos del símbolo para poder obtener el valor de la propiedad. Observa que necesitamos el símbolo original (almacenado en sfoo), crear otro símbolo a partir de la misma cadena, no sirve. Los símbolos son únicos.

Por lo tanto ahora podemos simular variables privadas de la siguiente manera:

  1. const s_name = Symbol("name");
  2. export default class Foo {
  3.     constructor(name) {
  4.         this[s_name] = name;
  5.     }
  6.     get name() {
  7.         return this[s_name];
  8.     }
  9. }

Los objetos de la clase Foo tienen una variable cuyo nombre es el símbolo guardado en s_name. Esa variable “no es realmente pública”, pero no hay manera de encontrar su nombre fuera del módulo que declara la clase (la variable s_name que contiene el símbolo, es privada del módulo por lo que no es accesible desde el exterior). Observa que no basta que alguien mire el código fuente del módulo y se cree otro Symbol(“name”) para acceder a la variable “privada”: los símbolos son únicos, o lo que es lo mismo: Symbol("a") != Symbol("a").

Este método no genera variables totalmente privadas. Las variables que están almacenadas en una propiedad cuyo nombre es un símbolo, no se listan en Object.getOwnPropertyNames y tampoco participan de la iteración de for..in pero se pueden obtener todos los símbolos asociados a un objeto mediante Object.getOwnPropertySymbols, que devuelve un array con todos los símbolos usados como nombre de propiedad del objeto. Basta con usar los símbolos devueltos para acceder a las variables “privadas”.

Por lo tanto este sistema, vendría a equivaler a la visibilidad private de C# para entendernos: Por defecto es imposible acceder a la propiedad, pero tenemos un mecanismo (en C# es Reflection) que nos permite saltarnos esa protección y acceder al contenido de la variable “privada”.

Weak maps

El uso de weak maps nos permite tener variables completamente privadas. Para ello usamos el nuevo tipo WeakMap de ES2015, que básicamente es un diccionario clave, valor, pero es especial porque no impide al GC de JavaScript recolectar todos aquellos valores almacenados en el weak map, siempre y cuando la referencia a la clave que tenga el weak map sea la única restante. Es decir, a todos los efectos el weak map no cuenta para el GC: aunque un objeto esté almacenado en él, si la clave usada no está referenciado por ningún otro objeto, el GC lo podrá eliminar.

El código usando WeakMap es el siguiente:

  1. const privates = new WeakMap();
  2. export default class Foo {
  3.     constructor(name) {
  4.         privates.set(this, {name: name});
  5.     }
  6.     get name() {
  7.         return privates.get(this).name;
  8.     }
  9. }

La idea es muy sencilla: por cada objeto que se cree de Foo, se almacena un valor en el WeakMap privates. La clave de dicho valor es this, que es el propio objeto Foo que se crea. Este objeto (privates.get(this)) almacena todo el estado “privado” del objeto Foo en cuestión. Gracias al hecho de que privates es un WeakMap no hay memory leak: Cuando el objeto Foo deje de usarse podrá ser eliminado por el garbage collector, aunque esté referenciado por el WeakMap como una de las claves usadas (precisamente por ser un privates Weak Map).

La clave (nunca mejor dicho :P) del asunto está en usar this como clave del Weak Map privates.

Al igual que en el caso del símbolo, el Weak Map es una variable privada del módulo, por lo que no se puede acceder a fuera desde él. Y a diferencia del caso del símbolo, ahora si que no hay nada que nadie pueda hacer para acceder a la variable privada que almacena el valor del nombre.

Saludos!

¿Qué hace ‘new’ en JavaScript?

Si preguntas a mucha gente cual es el objetivo de new en JavaScript te dirán que el de crear objetos. Y estarán en lo cierto, aunque esa definición no es del todo precisa. Y es que mucha gente no entiende exactamente que hace new así que vamos a dedicarle este post.

Creación de objetos sin new

La verdad es que new no es necesario en JavaScript. Se pueden crear objetos perfectamente sin necesidad de usar new:

  1. var foo = {
  2.     name: 'foo',
  3.     getUpperName: function () {
  4.         return this.name.toUpperCase();
  5.     }
  6. }

Esta manera de crear objetos la conocemos como object notation y nos permite definir objetos con campos y métodos. Por supuesto, en este caso solo creamos un objeto pero basta con declarar una función que tenga ese código para crear tantos objetos como queramos:

  1. var Foo = function (name) {
  2.     var foo = {
  3.         name: name || '',
  4.         getUpperName: function () {
  5.             return this.name.toUpperCase();
  6.         }
  7.     }
  8.  
  9.     return foo;
  10. }
  11.  
  12. var f2 = Foo("f2");
  13. f2.getUpperName();  // devuelve F2

Nos podemos preguntar cual es el prototipo de cada objeto creado mediante una llamada a Foo. Pues, como es de esperar, el prototipo de f2 será Object.prototype.

Podríamos indicar que los objetos creados por Foo heredan de Foo.prototype, con un código similar al:

  1. var Foo = function (name) {
  2.     var foo = {
  3.         name: name || '',
  4.         getUpperName: function () {
  5.             return this.name.toUpperCase();
  6.         }
  7.     }
  8.     Object.setPrototypeOf(foo, Foo.prototype);
  9.     return foo;
  10. }
  11.  
  12. var f2 = Foo("f2");
  13. f2.getUpperName();  // devuelve F2
  14. f2 instanceof Foo; // devuelve true

Lo interesante es que establecer como prototipo del objeto creado el objeto Foo.prototype, automáticamente hace que instanceof Foo devuelva true sobre todos los objetos creados por la función Foo. Vale, estoy usando Object.setPrototypeOf que es una función nueva de ES2015, pero en ES5 clásico tenemos Object.create que nos permite hacer lo mismo:

  1. var Foo = function (name) {
  2.     var foo = Object.create(Foo.prototype);
  3.     foo.name = name || '';
  4.     foo.getUpperName = function () {
  5.         return this.name.toUpperCase();
  6.     }
  7.     return foo;
  8. }
  9.  
  10. var f2 = Foo("f2");
  11. f2.getUpperName();  // devuelve F2
  12. f2 instanceof Foo; // devuelve true

¿Entonces… qué hace new?

Bueno, new se introdujo para dar una “sintaxis familiar” a los desarrolladores que venían de Java, pero básicamente hace lo mismo que hemos hecho nosotros. En JavaScript hablamos de funciones constructoras, pero no hay sintaxis para declarar una función constructora. Una función constructora es aquella pensada para ser llamada con new, pero que quede claro: es usar o no new al invocar una función lo que convierte a esa en constructora.

Cuando usamos new en una función, llamémosla Bar, ocurre lo siguiente:

  1. Se crea automáticamente un objeto vacío que hereda de Bar.prototype
  2. Se llama a la función Bar, con el valor de this enlazado al objeto creado en el punto anterior
  3. Si (y solo sí) la función Bar devuelve undefined, new devuelve el valor de this dentro de Bar (es decir, el objeto creado en el punto 1).

Como, gracias al punto 2, dentro de una función llamada con new el valor de this es el objeto creado en el punto 1, decimos que en una función constructora el valor de this es “el objeto que se está creando”. Y es por ello que las codificamos de esa manera:

  1. var Foo = function (name) {    
  2.     this.name = name || '';
  3.     this.getUpperName = function () {
  4.         return this.name.toUpperCase();
  5.     }
  6. }

Pero para que veas exactamente hasta que punto new no era “imprescindible” es porque incluso podríamos hacer una especie de polyfill para new:

  1. var functionToCtor = function (ctor) {
  2.     var o = Object.create(ctor.prototype);
  3.     var args = Array.prototype.slice.call(arguments)
  4.     args.splice(0, 1);
  5.     var v = ctor.apply(o, args);
  6.     return v === undefined ? o : v;
  7. }

La función functionToCtor hace lo mismo que new. Es decir, en este caso dada la función constructora Foo ambas expresiones son equivalentes:

  1. var foo = functionToCtor(Foo, "foo");
  2. var foo2 = new Foo("foo2");

Tanto foo como foo2:

  • Tienen como prototipo Foo.prototype
  • Tienen como constructor la función Foo
  • Devuelven true a la expresión instanceof Foo

¿Y si una función constructora devuelve un valor?

Esa es buena… Pues en este caso cuando se use new el valor obtenido será el devuelto por la función constructora (siempre que este valor sea distinto de undefined):

  1. var Foo = function (name) {    
  2.     this.name = name || '';
  3.     this.getUpperName = function () {
  4.         return this.name.toUpperCase();
  5.     }
  6.  
  7.     return { a: 10 };
  8. }
  9.  
  10. var f = new Foo();
  11. f.a;        //  10
  12. f instanceof Foo // false

Si… a pesar de que f ha sido creado usando new Foo, el prototipo de f ya no es Foo.prototype, ya que f vale el objeto devuelto por la función Foo. En este caso el objeto “original” cuyo prototipo es Foo.prototype y al que nos referíamos con this dentro de Foo se ha perdido. Lo mismo ocurre si usas el “polyfill” functionToCtor que hemos visto antes.

Funciones duales

Podemos hacer una función que sepa si se ha invocado con new o no? Pues sí:

  1. var Foo = function (name) {
  2.     if (this instanceof Foo) {
  3.         // Invocada con new
  4.     }
  5.     else {
  6.         // Invocada sin new
  7.     }
  8. }

Esto nos permite trabajar con this en el caso de que se haya usado new o bien no trabajar con this y devolver un valor en el caso de que no se haya invocado a la función con this. De todos modos si quieres que el usuario obtenga el mismo valor use new o no, lo más fácil es devolver el valor desde la función constructora. En este caso, eso sí, pierdes el hecho de que el prototipo del objeto sea la función constructora. Si quieres asegurarte que el usuario obtiene siempre un objeto con el prototipo asociado a la función constructora, es decir como si siempre usase new, puedes hacer:

  1. var Foo = function (name) {
  2.     var _create = function (name) {
  3.         this.name = name || '';
  4.         this.getUpperName = function () {
  5.             return this.name.toLocaleUpperCase();
  6.         }
  7.     }
  8.  
  9.     if (this instanceof Foo) {
  10.         _create.bind(this)(name);
  11.     }
  12.     else {
  13.         var o = Object.create(Foo.prototype);
  14.         _create.bind(o)(name);
  15.         return o;
  16.     }
  17. }

La función _create es la que realmente rellena el objeto (le llega ya creado en la variable this). La función Foo lo único que hace es mirar si ha sido llamada con new o no. En el primer caso llama a _create pero vinculando el valor de this dentro de _create al propio valor de this dentro de Foo (que en este caso es el objeto que se está creando) y no devolver nada. En el caso que el usuario no haya usado new la función Foo crea un objeto vacío, le asigna Foo.prototype como prototipo y luego llama a _create vinculando el valor de this dentro de _create al del objeto que acaba de crear. Y finalmente, en este caso, devuelve el objeto creado.

Así, tanto si el usuario hace var x = Foo() como var x2 = new Foo() obtiene lo mismo: un objeto cuyo prototipo es Foo.prototype y que por lo tanto devuelve true a instanceof Foo.

Espero que este post os haya ayudado a entender como funciona new en JavaScript.

Saludos!

La dualidad objeto-función en JavaScript

No sé si Brendan Eich es un amante de la física cuántica, pero al menos viendo algunas de las características, así lo parece. No solo tenemos el principio de incertidumbre de this si no que también tenemos el hecho de que en JavaScript un objeto puede comportarse como una función y viceversa, es decir una dualidad objeto-función.

Tomemos jQuery p. ej. Por defecto nos define el archiconocido $, con el cual podemos hacer algo como $(“#mydiv”), es decir, usarlo como una función, pero también podemos hacer $.getJSON(“…”), es decir, usarlo como un objeto. A ver como podemos conseguir esa dualidad es a lo que vamos a dedicar este post 🙂

La dualidad ya existente

La verdad… es que JavaScript ya viene con esa dualidad de serie… Todas las funciones de JavaScript se comportan a su vez como objetos (de hecho, en JavaScript las funciones son un tipo específico de objeto). O si no, observa el siguiente código:

  1. var f = function () {
  2.     console.log("i am a function, or an object?");
  3. }
  4.  
  5. console.log(typeof (f));
  6. console.log(f.toString());

El primer console.log, imprime “function”, dejando bien claro que f es una función. Pero luego observa que se está llamando al método toString de la función f. De la función en sí, no del valor devuelto por dicha función (lo que, ya puestos, generaría un error, pues f devuelve undefined). Así pues, las funciones tienen propiedades y métodos. Puede parecer chocante, pero en JavaScript los funciones, son también, objetos.

En JavaScript cada objeto tiene asociado su prototipo que es otro objeto (y dado que el prototipo es un objeto tiene asociado su propio prototipo, lo que genera una cadena de prototipos, que termina en un objeto llamado Object.prototype y que su prototipo es null).

Existe una propiedad, llamada __proto__, que nos permite acceder al prototipo de un objeto en concreto. Cuando creamos un objeto, usando object notation dicha propiedad se asigna a Object.prototype (como puedes comprobar en esa captura de pantalla de las herramientas de desarrollador de Chrome):

image

Las funciones, como objeto que son, tienen todas su propio prototipo que es… Function.prototype, que es por cierto el objeto que define las funciones call, apply y bind, que podemos llamar sobre cualquier función.

image

(Por si lo estás suponiendo, sí: el prototipo de Function.prototype es Object.prototype).

Entender bien la cadena de prototipado es fundamental, pues en ella se basa todo el sistema de herencia de JavaScript.

Ahora que ya vemos que las funciones en realidad son objetos, vamos a ver como podemos hacer como hace jQuery y colocar métodos propios en una función que definamos.

No nos sirve agregar métodos a Function.prototype: si lo hacemos esos métodos van a estar disponibles para todas las funciones y nosotros queremos que dichos métodos estén disponibles solo para una función en concreta (al igual que hace jQuery, que agrega métodos específicos pero solo a la función $).

Creando nuestra propia dualidad

Por extraño que parezca crear nuestra propia dualidad es muy sencillo, basta con el siguiente código:

  1. var f = function (name) {
  2.     return "Hello " + name;
  3. }
  4. f.foo = function () {
  5.     return "foo Utility method";
  6. }

Una vez tenemos esto podemos invocar a la función f, pero también a la función foo de la función f:

  1. var x = f("edu");
  2. var x2 = f.foo();

Ya tenemos a nuestra variable f, comportándose dualmente como una función y un objeto a la vez.

Por supuesto podemos refinar un poco esa técnica:

  1. var f = function (name) {
  2.     return "Hello " + name;
  3. }
  4. var fn = {
  5.     foo: function () {
  6.         console.log("Utility method");
  7.     }
  8. }
  9. Object.assign(f, fn);

Este código es equivalente al anterior, pero usa Object.assign (método de ES6) para copiar las propiedades contenidas en fn (el método foo) dentro del objeto (función) f.

Usando ES6 es muy fácil crearnos una función factoría que nos permita establecer un objeto con un conjunto de operaciones comunes a una función. Para ello podemos establecer este objeto como prototipo de la función (a su vez este objeto prototipo de la función debe “heredar” de Function.prototype para no perder las funciones que este provee).

  1. var factory = function (p, f) {
  2.     var fp = Object.create(Function.prototype);
  3.     Object.assign(fp, p);
  4.     Object.setPrototypeOf(f, fp);
  5.     return f;
  6. }

De este modo si tenemos un objeto cualquiera y queremos que todas sus operaciones sean heredadas por una función:

  1. var f = function () { return this; };
  2. factory({
  3.     dump: function () {
  4.         console.log('dump…');
  5.     }
  6. }, f);
  7. f.dump();

Ahora, la función f, además de los métodos definidos en Function.prototype (tales como apply o call) tiene también todos los métodos definidos en el objeto pasado como primer parámetro de factory, es decir el método dump.

Fácil y sencillo, ¿no?

Por cierto, ya puestos a divertirnos un poco. Cual es la salida del siguiente código si lo ejecutas en la consola JS del navegador?

  1. var f = function () { return this; };
  2. var obj = {
  3.     foo: f
  4. };
  5. factory({
  6.     unbind: function () {
  7.         return this.bind(this);
  8.     }
  9. }, f);
  10. console.log("f()", f());
  11. console.log("f.bind({a:10})()", f.bind({ a: 10 })());
  12. console.log("f.unbind()()", f.unbind()());
  13. console.log("obj.foo()", obj.foo());
  14. console.log("obj.foo.unbind()()", obj.foo.unbind()());

Piénsalo un poco con calma… si aciertas los valores de los 5 console.log eres de los que dominan el principio de incertidumbre de this 😉

Saludos!