Seleccionar página

public-privateUna de las cosas que más sorprende cuando uno empieza a trabajar con las clases de ECMAScript 2015 (ES6) es que no dispone de un sistema predefinido para definir propiedades o métodos privados y por lo tanto para gestionar la encapsulación u ocultamiento del estado, es decir, que los datos miembro de un objeto sólo se puedan cambiar mediante las operaciones definidas para ese objeto quedando ocultos y protegidos contra su modificación por quien no tenga derecho a acceder a ellos.

Vamos a repasar los distintos métodos que tenemos para conseguir esta encapsulación.

Propiedades públicas

Como base para el resto de ejemplos vamos a implementar una primera versión de nuestra serie de Fibonacci. Simplemente llamamos al constructor con el número de elementos de la serie que queremos obtener, se guardan en una propiedad values y podemos acceder a ellas directamente desde esa propiedad o por medio del método generador que creamos con Symbol.iterator:

"use strict";

class Fibonacci {
    constructor(n) {
        this.values = [0,1];
        for(var i = 1; i < n ; i++) {
            this.values.push(this.values[i] + this.values[i - 1]);
        }
    }
    * [Symbol.iterator]() {
        for (let arg of this.values) {
            yield arg;
        }
    }
}

var f10 = new Fibonacci(10);

// Acceso por el iterador
for (var i of f10) {
    console.log(i);
}

// Acceso por la propiedad values
for (var v of f10.values) {
    console.log(v);
}

El problema que presenta este modelo es que values se puede escribir sin problemas y podríamos modificar o ampliar su contenido sin ninguna limitación:

var f10 = new Fibonacci(10);

f10.values[0] = NaN;
f10.values[1] = NaN;
f10.values.push('other');

Propiedades que empiezan por _

Un modelo utilizado desde hace tiempo es simplemente utilizar propiedades con nombres que empiezan por _. Realmente no produce ningún tipo de efecto real, pero es una convención generalmente aceptada para describir miembros privados de un objeto y que no deben ser utilizados o, al menos, deben utilizarse con gran cuidado, ya que podemos cambiar el comportamiento sin control.

"use strict";

class Fibonacci {
    constructor(n) {
        this._values = [0,1];
        for(var i = 1; i < n - 1; i++) {
            this._values.push(this._values[i] + this._values[i - 1]);
        }
    }
    * [Symbol.iterator]() {
        for (let arg of this._values) {
            yield arg;
        }
    }
}

Utilización del constructor

Una opción es volver al modelo de ES5 y realizar la mayor parte de las operaciones en el constructor y de esta forma utilizar la closure de esta función para utilizar las variables definidas en ese alcance:

"use strict";

class Fibonacci {
    constructor(n) {
        var values = [0,1];
        for(var i = 1; i < n - 1; i++) {
            values.push(values[i] + values[i - 1]);
        }
        
        this[Symbol.iterator] = function* () {
            for (let arg of values) {
                yield arg;
            }
        };
    }
}

var f10 = new Fibonacci(10);

// Acceso por el iterador
for (var i of f10) {
    console.log(i);
}

Funciona, aunque parece que estemos haciendo las cosas como en el ES5. Incluir el cuerpo de los métodos en constructor y hacerlos públicos con this es posible, aunque se nos antoja poco elegante.

Métodos del objeto Object para controlar el acceso

Es curioso que muchos programadores en Javascript desconozcan las enormes posibilidades de control que nos ofrecen un pequeño puñado de métodos de Object. Por medio de estos métodos podemos controlar cómo se accede a un objeto o sus miembros, pudiendo evitar que se borre, modifique o sea enumerado. Os recomendamos dar un vistazo a la documentación de Object.

En nuestro caso podemos utilizar estos objetos dentro del constructor para controlar el acceso a las propiedades que queremos proteger de nuestro objeto:

"use strict";

class Fibonacci {
    constructor(n) {
        var values = [0,1];
        for(var i = 1; i < n - 1; i++) {
            values.push(values[i] + values[i - 1]);
        }
        Object.defineProperty(this, 'elements', {value: n, writable: false});
        Object.defineProperty(this, 'values', {value: values, writable: false});
        Object.freeze(this.values);
    }
    * [Symbol.iterator]() {
        for (let arg of this.values) {
            yield arg;
        }
    }
}

var f10 = new Fibonacci(10);

f10.values[0] = NaN;      // TypeError: Cannot assign to read only property
f10.values[1] = NaN;      // TypeError: Cannot assign to read only property
f10.values.push('other'); // TypeError: Cannot assign to read only property
f10.elements = 20;        // TypeError: Cannot assign to read only property

delete f10.values;        // TypeError: Cannot delete property
delete f10.elements;      // TypeError: Cannot delete property

Realmente no hemos ocultado estas propiedades, pero al menos hemos evitado que se modifiquen o se borren, por lo que protegemos el funcionamiento de nuestros objetos de manipulaciones no previstas.

Uso de Symbol

ECMAScript 2015 (ES6) ofrece un interesante objeto denominado Symbol. En nuestro ejemplo ya estamos usando Symbol.iterator como nombre para nuestro método generador que es utilizado para iterar el contenido. Ahora vamos a utilizar Symbol() para crear un nombre de propiedad que quedará oculto del resto de miradas.

"use strict";

var values = Symbol();

class Fibonacci {
    constructor(n) {
        this[values] = [0,1];
        for(var i = 1; i < n - 1; i++) {
            this[values].push(this[values][i] + this[values][i - 1]);
        }
    }
    * [Symbol.iterator]() {
        for (let arg of this[values]) {
            yield arg;
        }
    }
}

var f10 = new Fibonacci(10);

// Acceso por el iterador
for (var i of f10) {
    console.log(i);
}

Si este código se utilizar en un módulo la variable values que contiene el símbolo no estará disponible, pero si usamos este código en otro contexto tiene el problema de que expone públicamente la variable values y podríamos llegar a acceder al valor de la matriz con f10[values]. Para evitar esto podemos hacer uso de un nuevo alcance por medio de un sencillo par de corchetes y let:

"use strict";

var Fibonacci;
{
    let values = Symbol();

    Fibonacci = class Fibonacci {
        constructor(n) {
            this[values] = [0,1];
            for(var i = 1; i < n - 1; i++) {
                this[values].push(this[values][i] + this[values][i - 1]);
            }
        }
        * [Symbol.iterator]() {
            for (let arg of this[values]) {
                yield arg;
            }
        }
    };
}

var f10 = new Fibonacci(10);

// Acceso por el iterador
for (var i of f10) {
    console.log(i);
}

Esto ya se acerca bastante a lo que queremos, ya que values no es accesible y queda completamente protegido.

Una anotación a partir de un comentario que nos ha hecho David García Paredes, al cual le agradecemos mucho su aportación: si utilizamos directamente una variable dentro del alcance definido por el par de corchetes y guardamos ahí los valores, esta variable es común para todas las instancias y se produciría un problema si instanciamos dos objetos de la clase Fibonacci:

"use strict";
 
var Fibonacci;
{
    let values;
    Fibonacci = class Fibonacci {
        constructor(n) {
            values = [0,1];
            for(var i = 1; i < n - 1; i++) {
                values.push(values[i] + values[i - 1]);
            }
        }
        * [Symbol.iterator]() {
            for (let arg of values) {
                yield arg;
            }
        }
    };
}
 
var f10 = new Fibonacci(10);
var f20 = new Fibonacci(20); 

// Acceso por el iterador
for (var i of f10) {
    console.log(i);  // Se ejecutará 20 veces
}

WeakMap

Una variante interesante del modelo anterior es utilizar WeakMap para almacenar los datos protegidos. El objeto WeakMap que incorpora ES6 y es una colección de pares clave/valor en la que las claves son objetos y los valores son valores arbitrarios. Como clave podemos usar la referencia a cada instancia por medio de this y como valores un objeto con todo aquello que queremos mantener como privado.

"use strict";

var priv = new WeakMap();

class Fibonacci {
    constructor(n) {
        var values = [0,1];
        for(var i = 1; i < n - 1; i++) {
            values.push(values[i] + values[i - 1]);
        }
        priv.set(this,{values});
    }
    * [Symbol.iterator]() {
        for (let arg of priv.get(this).values) {
            yield arg;
        }
    }
}

var f10 = new Fibonacci(10);

// Acceso por el iterador
for (var i of f10) {
    console.log(i);
}

Como en el caso anterior, si utilizamos este código en un módulo el acceso a priv estará limitado a este módulo, pero en otros contextos estamos exponiendo ese objeto ante ojos indiscretos que podrían llegar a utilizar priv.get(f10).values. Para evitar esto modelos ocultar este objeto de la siguiente forma:

"use strict";

var Fibonacci;
{
    let priv = new WeakMap();
    Fibonacci = class Fibonacci {
        constructor(n) {
            var values = [0,1];
            for(var i = 1; i < n - 1; i++) {
                values.push(values[i] + values[i - 1]);
            }
            priv.set(this,{values});
        }
        * [Symbol.iterator]() {
            for (let arg of priv.get(this).values) {
                yield arg;
            }
        }
    };
}

var f10 = new Fibonacci(10);

// Acceso por el iterador
for (var i of f10) {
    console.log(i);
}

En este caso podemos almacenar cuantos valores necesitamos dentro del WeakMap sin exponer este objeto.

Conclusiones

Es lamentable que no dispongamos de un modelo más sencillo y plástico para gestionar el encapsulamiento en las clases de ECMAScript 2015. Existen alternativas y formas de obtener este comportamiento, pero hubiera sido deseable poder utilizar algún tipo de cláusula con la que describir esta circunstancia de forma directa. Parece que en ES7 es posible que haya algo de este tipo, pero de momento nos tenemos que conformar con alguno de estos métodos.

Si quieres saber más, te recomendamos este enlace que nos ha enviado Ángel: http://sravi-kiran.blogspot.com.es/2015/01/PrivateMembersInES6Classes.html.

Novedades

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

En esta charla, organizada por MadridJS, Pablo Almunia nos muestra cómo la mayoría de nosotros cuando oímos hablar por primera vez de HTTP2 nos ilusionamos con las posibilidades que presumiblemente se abrían para el desarrollo de soluciones web avanzadas y cómo muchos nos sentimos defraudados con lo que realmente se podía implementar.

En esta charla podemos ver cómo funciona el HTTP2, que debemos tener en cuenta en el servidor para hace uso de este protocolo y, sobre todo, cómo podemos enviar información desde el servidor al cliente de forma efectiva y fácil. Veremos con detenimiento cómo por medio de los Server-Sent Events (SSE) podemos recibir en el cliente datos enviados desde el servidor sin utilizar websocket, simplificando enormemente la construcción de aplicaciones con comunicación bidireccional.

Observables en Javascript con Proxies

Observables en Javascript con Proxies

En esta charla, organizada por MadridJS, Pablo Almunia nos habla de la observación reactiva de objetos en Javascript por medio de Proxy. Se describe paso a paso cómo funcionan los Proxies y en que casos pueden ser nuestro mejor aliado. Veremos que no hay que tenerles miedo, son bastante sencillos de utilizar, y nos ofrecen una gran abanico de posibilidades.

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

En esta charla de Madrid JS, Rafael Ventura nos describe las funcionalidades clave de NextJS, nos muestra en vivo cómo desarrollar una completa aplicación JAMStack con Server Side Rendering (SSR) y Static Site Generation (SSG) y termina mostrando como publicar esta aplicación en Vercel.

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

En esta charla Rubén Aguilera nos cuenta los problemas que tienen muchas empresas a la hora de sacar productos accesibles, vistosos y usables en el Time To Market que requiere Negocio y cómo podemos minimizar este tiempo gracias al DevUI con StencilJS para adecuar una aplicación de Angular a las exigencias del mercado en tiempo record.