Seleccionar página

Breve introducción a la inmutabilidad

El desarrollo con datos inmutables es un principio fundamental de la programación funcional que se está extendiendo también en la programación orientada a objeto. Su planteamiento básico es muy sencillo: un dato u objeto, una vez creado, no puede ser cambiado, manteniendo su estado original en todo momento. Si por algún motivo se tuviera que cambiar el dato, entonces se obtendría una copia con los datos modificados, pero nunca se cambian los valores originales.

Lo contrario a la inmutabilidad es la mutabilidad, es decir, la capacidad para cambiar el valor o el estado de los elementos de un lenguaje de programación. Cuando cambiamos el valor de una propiedad, o la referencia de una variable, estamos haciendo uso la mutabilidad, es decir, de la capacidad de cambiar.

El uso de estructuras mutables es una de las razonas esgrimidas para el déficit de confianza en muchos lenguajes, incluido Javascript. Este es un lenguaje que permite una gran capacidad de mutación de sus elementos, prácticamente cualquier dato puede transformarse en algo diferente en un momento determinado.

Se suele indicar que la mutación es la principal causa de los efectos secundarios, es decir, cuando varias partes del código cambian un mismo dato, es muy probable que se produzcan errores, ya que determinado código espera encontrarse con unos determinados datos y se encuentra con otros bien diferentes, ya que han sido cambiado en otra parte de nuestra aplicación.

También se argumenta que la mutación es una causa de los estados impredecibles en los datos y objetos, lo que exige una programación defensiva, comprobando que los datos son del tipo que esperamos, están completos o los objetos siguen en el mismo estado interno y no han sido cambiados después de haber sido pasados a otra función.

Se completa esta crítica indicando que la mutación requiere una depuración compleja en tiempo de ejecución. Cuando se analiza un error hay trazar el código intentado descubrir qué es lo que se cambió inesperadamente, que contenido tienen las variables en cada punto de ejecución del programa, dónde se realizaron los cambios de estado o de valores y por qué unas veces lo encontramos de una forma y otras veces de otra.

La inmutabilidad no significa que no haya cambios. Esto no sería realmente razonable, las cosas cambian, los usuarios modifican los valores y los estados, pero cualquier modificación debe reflejarse creando un nuevo estado y manteniendo el original sin cambios. Es decir, se genera es una nueva versión del estado que refleje el cambio, se obtiene un nuevo objeto con el cambio aplicado, pero no se modifica el objeto original.

Immutabilidad en Javascript

Aunque Javascript es extremadamente flexible y tiene una gran capacidad de mutación, tenemos un ejemplo bastante sencillo de inmutabilidad, que facilita mucho la comprensión de este concepto: las cadenas de texto son siempre inmutables. Si intentamos modificar una cadena, veremos que realmente no es posible, cómo mucho obtendremos otra cadena con diferentes valor.

En modo no estricto podemos intentar cambiar un carácter de nuestra cadena, pero no lo conseguiremos, el intento de cambio falla silenciosamente y no se consigue cambiar la cadena:

let x = "hola mundo";
console.log(x); // hola mundo
x[0] = 'H';
console.log(x); // hola mundo

En modo estricto esta misma operación lanzará un error:

"use strict";
let x = "hola mundo";
console.log(x); // hola mundo
try {
  x[0] = 'H';
} catch(err) {
  console.error(err.message); // Cannot assign to read only property '0' of string 'hola mundo'
}
console.log(x); // hola mundo

Cuando creemos modificar una cadena, realmente estamos obteniendo otra, manteniendo la primera intacta, ya que ningún método de manipulación de string modifica la cadena de texto original:

let x = "hola mundo";
let y = x.replace('h', 'H');
console.log(x); // hola mundo
console.log(y); // Hola mundo
console.assert(x !== y);

Esto, que es muy fácil de comprender en las cadenas, es igual en todos los tipos básicos de Javascript. En realidad, no podemos cambiar un valor numérico o un valor booleano, lo que estaremos haciendo como mucho es sustituir uno por otro, pero el valor como tal es inmutable.

Por otro lado, la aparición de la instrucción const ha incorporado en Javascript una forma sencilla de establecer una variable como inmutable, es decir, que no puede cambiarse el valor que contiene.

var a = 1;
a = 2;
let b = 1;
b = 2;
const c = 1;
c = 2; // Throw error "Assignment to constant variable."

Lo que probablemente más confunde en Javascript es la forma en la que los objetos se referencian en las variables, ya que no es el objeto como tal el que está contenido en la variable, si no una referencia (en forma de puntero) es. En este caso la instrucción const no bloquea los cambios en el objeto, sólo bloquea los cambios entre una referencia y otra.

const obj = {a: 1};

obj.a = 2; // OK

obj = {a:3}; // KO: Throw error "Assignment to constant variable."

Objetos inmutables en Javacript: principios básicos

Vamos a ver cómo podemos hacer que los objetos sean también inmutables en Javascript. Afortunadamente el lenguaje nos da varios mecanismos para conseguir nuestro objetivo, mostrando una vez más su enorme flexibilidad.

Aproximación vía Proxy

Una primera aproximación es a la obtención de una versión inmutable de un objeto por medio de un Proxy que intercepte las acciones defineProperty y deleteProperty. Con estas dos capturas no es necesario que también capturemos la acción set, ya que esta, internamente, llama a defineProperty.

function immutableObject (obj) {
  return new Proxy (obj, {
    defineProperty (target, p, attributes) {
      throw new TypeError (`Cannot assign to read only property '${ p }' of object '#<Object>'`)
    },
    deleteProperty (target, p) {
      throw new TypeError (`Cannot delete property '${ p }' of object #<Object>`)
    }
  });
}

Si pasamos un objeto por esta función, ya no podremos modificar sus propiedades sin que se lance un error:

const obj = immutableObject({a: 1});
obj.a = 2; // Error: Cannot assign to read only property 'a' of object '#<Object>'

El primer problema que tenemos con es que esta protección sólo se hace en el primer nivel del objeto, es decir, en las propiedades definidos directamente en él, y no en propiedades que puedan contenerse en otros niveles más profundos. Es lo que se suele llamar cambio superficial, y nosotros vamos a necesitar un cambio en profundidad. Para ello podemos ajustar nuestra función de la siguiente forma:

function immutableObject (obj) {
  return typeof obj !== 'object' ?
    obj :
    console.log (Object.values (obj)) ||
    Object.keys (obj).forEach (prop => obj[ prop ] = immutableObject (obj[ prop ])) ||
    new Proxy (obj, {
      defineProperty (target, p, attributes) {
        throw new TypeError (`Cannot assign to read only property '${ p }' of object '#<Object>'`)
      },
      deleteProperty (target, p) {
        throw new TypeError (`Cannot delete property '${ p }' of object #<Object>`)
      }
    })
}

 

Aunque esta es una aproximación válida, lo cierto es que puede resultar un poco engorrosa, ya que el trabajo con Proxys no siempre resulta cómodo. Veamos una alternativa:

Object.freeze()

El método Object.freeze() congela el objeto e impide que cambiemos sus propiedades o las borremos. Como suele ser habitual en Javascript, este cambio se hace de forma superficial y no afecta a los objetos que se contengan dentro de las propiedades. Para resolver esta limitación podemos crear una función que aplique de forma recursiva Object.freeze():

const immutableObject = (obj) =>
  typeof obj === 'object' ?
    Object.values (obj).forEach (immutableObject) || Object.freeze (obj) :
    obj;

Ahora, cualquier cambio en las propiedades del objeto lanzará un error en modo estricto y será ignorada en modo no estricto. Esta diferencia es la misma que vimos anteriormente cuando intentábamos cambiar una cadena de texto.

const obj = immutableObject({p: {a: 1}});
obj.p.a = 2;  // Error: Cannot assign to read only property 'a' of object '#<Object>'

Con esta aproximación se consigue una bastante completa gestión de objetos inmutables, pero no para todos los tipos de objetos. Vamos a ver por qué…

Objetos inmutables: una función para congelarlos a todos

Como ya hemos visto en varias ocasiones, Javascript dispone de una importante cantidad de tipos de objetos, no sólo los literales definidos con llaves { }, también son objetos los Array, Date, Map, Set, ArrayBuffer, etc. Tenemos que encontrar una forma de poder congelar en profundidad el estado de todos estos objetos.

Para abordar esta función, en primer lugar, vamos a ver que pasa con un sencillo objeto, por ejemplo, una fecha. Un objeto de tipo Date(). Si lo bloqueamos con Object.freeze() no podremos añadir nuevas propiedades al objeto, pero sí podremos cambiar su valor interno, por ejemplo con .setFullYear().

const d = new Date('2019-01-01T00:00:00.000Z');

Object.freeze(d);

console.log('antes', d);

d.setFullYear(d.getFullYear() - 100);

console.log('después', d);

Para poder congelar de forma efectiva un objeto Date deberíamos eliminar la posibilidad de cambiar su estado interno con todos los métodos que empiezan con set…(). Esto lo podríamos hacer por medio de un Proxy. Cuando se soliciten los métodos mutadores, podemos devolver un error o crea una nueva versión de estos métodos que devuelvan una nueva fecha sin modificar la actual.

En el caso de los objetos Array hay bastantes métodos, por ejemplo como .sort(), que modifican completamente la estructura de la matriz. En el caso de Array, si hemos utilizado Object.freeze() sí obtendremos un error al intentar ejecutar un método que modifica su contenido, ya que este cambio se ve reflejado en las propiedades del objeto y estas han sido congeladas.

const a = ['a', 'c', 'e', 'f', 'b', 'd'];

Object.freeze(a);

a.sort(); // Cannot assign to read only property '0' of object '[object Array]'

Aunque hemos conseguido bloquear el objeto, quizás sería interesante poder aplicar una técnica similar a la que hemos descrito antes con Date y hacer que estos métodos que mutan el objeto devuelvan un nuevo objeto, dejando el primero sin cambios. Esto se puede aplicar a Array, Map, Set, etc.

Por último debemos revisar que objetos de los que podemos crear en Javascript no tiene mucho sentido que los bloqueemos. Por ejemplo, no tiene mucho sentido que intentemos bloquear el estado de un objeto tipo Promise o de un iterador. Su propia naturaleza exige que su estado interno se pueda cambiar. También tenemos algunos objetos como WeakMap o WeakSet que por su propio funcionamiento es imposible que consigamos bloquerarlos ya que las referencias que contienen son eliminadas de forma automática por el recolector de basura de Javascript.

Aunque en proceso de estandarización hubo una cierta discusión sobre si se podría o no aplicar Object.freeze() a los Typed Array y finalmente en la especificación se indica que se debe producir un error si se intentan hacer inmutables cualquier objeto que hereda de TypedArray. Lo cierto es que aplicando nuestra técnica de reescritura de los métodos mutadores podemos crear objetos del tipo TypedArray inmutables que devuelvan nuevos ArrayBuffer cuando se intenten hacer cambios sobre los valores originales, aunque no podamos aplicar Object.freeze() sobre ellos.

Utilidad Immutable

Para poder gestionar con facilidad la inmutabilidad de todos los tipos de objetos de Javascript y que de forma razonable podemos crear como inmutables, hemos creado una pequeña utilidad que convierte cualquier objeto en Immutable y que consta de las siguientes funciones:

  • Immutable( object ) o Immutable.create( object ): devuelve un objeto inmutable en profundidad (todos los objetos contenidos en el objeto también son inmutables). Si el objeto tiene métodos que devuelven nuevos objetos o que intentan modificar el objeto, entonces -si hemos sido capaces de identificarlos- los cambiamos por métodos que devuelven una copia inmutable del objeto con el cambio.
  • Immutable.assign(target, origin): realiza aplica las propiedades del objeto origin en en el objeto target, devolviendo una copia inmutable y manteniendo target sin cambios. Este cambio se produce en profundidad, es decir, a cualquier nivel del objeto y no sólo a nivel superficial.
  • Immutable.isImmutable(object) devuelve true si el objeto es inmutable.

El código fuente de esta utilidad es bastante pequeño y lo recogemos aquí con dos versiones, una con estilo funcional y otra con estilo imperativo (que cada uno lea y utilice la que considere más adecuada):

 

const Immutable = (() => {
  
  const ISIMMUTABLE = Symbol ('immutable object');
  const refs        = new WeakMap ();
  const ignoreTypes = [ 'ArrayBuffer',  'Array Iterator', 'AsyncGenerator', 'Generator',
    'String Iterator', 'Async Iterator', 'Map Iterator', 'Set Iterator',
    'RegExp String Iterator', 'Promise', 'SharedArrayBuffer', 'WeakMap', 'WeakSet' ];
  const TypedArray  = Object.getPrototypeOf (Int32Array);
  
  const handler = (mutators, constructor) =>
    (target, prop, receiver) =>
      prop === ISIMMUTABLE ?
        true :
        mutators && mutators.includes (prop) ?
          ((result) =>
              (...args) =>
                result[ prop ] (...args) || true ?
                  Immutable (result) :
                  void (0)
          ) (constructor (target, prop, receiver)) :
          ((result) =>
              (typeof result === 'function' && prop !== 'constructor') ?
                (...args) => Immutable (result.bind (target) (...args)) :
                result
          ) (Reflect.get (target, prop));
  
  const Immutable = (obj) =>
    ((typeof obj !== 'object' && typeof obj !== 'function') ||
     obj === null ||
     ignoreTypes.includes (obj[ Symbol.toStringTag ]) ||
     Object.isFrozen (obj)) ?
      obj :
      (refs.has (obj)) ?
        refs.get (obj) :
        ((result) =>
          refs.set (obj, result) &&
          (Reflect.ownKeys (obj)
                 .forEach((prop) =>
            result[ prop ] = Immutable (obj[ prop ])) ||
          !(obj instanceof TypedArray) ? Object.freeze (result) : result)
        )(
          (obj instanceof Date) ?
            new Proxy (obj, {
              get : handler (
                [ 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 'setMinutes',
                  'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
                  'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth',
                  'setUTCSeconds', 'setYear' ],
                (target, prop, receiver) => new target.constructor (target.valueOf ())
              )
            }) :
            (obj instanceof Array) ?
              new Proxy (obj, {
                get : handler (
                  [ 'pop', 'push', 'shift', 'unshift', 'splice', 'copyWithin', 'fill',
                    'sort', 'reverse' ],
                  (target, prop, receiver) => new target.constructor (...target)
                )
              }) :
              (obj instanceof Map) ?
                new Proxy (
                  [ ...obj.keys () ]
                    .reduce (
                      (map, key) => key ?
                        obj.set (key, Immutable (obj.get (key))) :
                        void (0),
                      obj), {
                    get : handler (
                      [ 'clear', 'delete', 'set' ],
                      (target, prop, receiver) => new target.constructor (target)
                    )
                  }) :
                (obj instanceof Set) ?
                  new Proxy (obj, {
                    get : handler (
                      [ 'add', 'clear', 'delete' ],
                      (target) => new target.constructor (obj)
                    )
                  }) :
                  (obj instanceof DataView) ?
                    new Proxy (obj, {
                      get : handler (
                        [ 'setBigInt64', 'setBigUint64', 'setFloat32',
                          'setFloat64', 'setInt16', 'setInt32',
                          'setInt8', 'setUint16', 'setUint32', 'setUint8' ],
                        (target) => new target.constructor (target.buffer.slice (0))
                      )
                    }) :
                    (obj instanceof TypedArray) ?
                      new Proxy (obj, {
                        get : handler (
                          [ 'copyWithin', 'fill', 'reverse', 'set', 'sort' ],
                          (target) => new target.constructor (target)
                        )
                      }) :
                      Object.defineProperty (
                        obj,
                        ISIMMUTABLE,
                        {enumerable : false, value : true}
                      )
        );
  
  const assign = (obj, newData) =>
    ((result) => {
      Reflect.ownKeys (newData)
             .filter (key => obj[ key ] !== newData[ key ])
             .forEach (key => result[ key ] =
               (typeof newData[ key ] !== 'object' ||
                Reflect.ownKeys (newData[ key ]).length === 0) ?
                 newData[ key ] :
                 assign (obj[ key ], newData[ key ]));
      Reflect.ownKeys (obj)
             .filter (key => typeof result[ key ] === 'undefined')
             .forEach (key => result[ key ] = obj[ key ]);
      return Immutable (result);
    }) (((proto) =>
        proto === null ? Object.create (null) :
          obj.constructor !== Object ? new obj.constructor () :
            proto !== Object.prototype ? Object.create (proto) :
              obj.constructor ()
    ) (Object.getPrototypeOf (obj)));
  
  const isImmutable = (obj) => obj[ ISIMMUTABLE ] ||
                               (typeof obj !== 'object' && typeof obj !== 'function');
  
  Immutable.assign      = assign;
  Immutable.create      = Immutable;
  Immutable.isImmutable = isImmutable;
  return Object.freeze (Immutable);
}) ();
const Immutable = (() => {
  
  const ISIMMUTABLE = Symbol ('immutable object');
  const refs        = new WeakMap ();
  const ignoreTypes = [ 'ArrayBuffer', 'Array Iterator', 'AsyncGenerator', 'Generator',
    'String Iterator', 'Async Iterator', 'Map Iterator', 'Set Iterator',
    'RegExp String Iterator', 'Promise', 'SharedArrayBuffer', 'WeakMap', 'WeakSet' ];
  const TypedArray  = Object.getPrototypeOf (Int32Array);
  
  function handler (mutators, constructor) {
    return function (target, prop, receiver) {
      if (prop === ISIMMUTABLE) {
        return true;
      }
      if (mutators && mutators.includes (prop)) {
        return function (...args) {
          const result = constructor (target, prop, receiver);
          result[ prop ] (...args);
          return Immutable (result);
        };
      }
      const result = Reflect.get (target, prop);
      if (typeof result === 'function' && prop !== 'constructor') {
        return function (...args) {
          return Immutable (result.bind (target) (...args));
        }
      }
      return result;
    };
  }
  
  function Immutable (obj) {
    if ((typeof obj !== 'object' && typeof obj !== 'function') ||
        obj === null ||
        ignoreTypes.includes (obj[ Symbol.toStringTag ]) ||
        Object.isFrozen (obj))
    {
      return obj;
    }
    if (refs.has (obj)) {
      return refs.get (obj);
    }
    let result;
    if (obj instanceof Date) {
      result = new Proxy (obj, {
        get : handler (
          [ 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 'setMinutes',
            'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
            'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth',
            'setUTCSeconds', 'setYear' ],
          (target, prop, receiver) => new target.constructor (target.valueOf ())
        )
      });
    } else if (obj instanceof Array) {
      result = new Proxy (obj, {
        get : handler (
          [ 'pop', 'push', 'shift', 'unshift', 'splice', 'copyWithin', 'fill',
            'sort', 'reverse' ],
          (target, prop, receiver) => new target.constructor (...target)
        )
      });
    } else if (obj instanceof Map) {
      for (let key of obj.keys ()) {
        obj.set (key, Immutable (obj.get (key)));
      }
      result = new Proxy (obj, {
        get : handler (
          [ 'clear', 'delete', 'set' ],
          (target, prop, receiver) => new target.constructor (target)
        )
      });
    } else if (obj instanceof Set) {
      result = new Proxy (obj, {
        get : handler (
          [ 'add', 'clear', 'delete' ],
          (target) => new obj.constructor (obj)
        )
      });
    } else if (obj instanceof DataView) {
      result = new Proxy (obj, {
        get : handler (
          [ 'setBigInt64', 'setBigUint64', 'setFloat32',
            'setFloat64', 'setInt16', 'setInt32',
            'setInt8', 'setUint16', 'setUint32', 'setUint8' ],
          (target, prop, receiver) => new target.constructor (target.buffer.slice (0))
        )
      });
    } else if (obj instanceof TypedArray) {
      result = new Proxy (obj, {
        get : handler (
          [ 'copyWithin', 'fill', 'reverse', 'set', 'sort' ],
          (target, prop, receiver) => new target.constructor (target)
        )
      });
    } else {
      result = obj;
      Object.defineProperty (
        result,
        ISIMMUTABLE,
        {enumerable : false, value : true}
      );
    }
    refs.set (obj, result);
    for (let prop of Reflect.ownKeys (obj)) {
      result[ prop ] = Immutable (obj[ prop ]);
    }
    if (!(obj instanceof TypedArray)) {
      Object.freeze (result);
    }
    return result;
  }
  
  function assign (obj, newData) {
    let result;
    const proto = Object.getPrototypeOf (obj);
    if (proto === null) {
      result = Object.create (null)
    } else if (obj.constructor !== Object) {
      result = new obj.constructor ()
    } else if (proto !== Object.prototype) {
      result = Object.create (proto);
    } else {
      result = obj.constructor ();
    }
    for (key of Reflect.ownKeys (newData)) {
      if (obj[ key ] !== newData[ key ]) {
        if (typeof newData[ key ] !== 'object' ||
            Reflect.ownKeys (newData[ key ]).length === 0)
        {
          result[ key ] = newData[ key ];
        } else {
          result[ key ] = assign (obj[ key ], newData[ key ]);
        }
      }
    }
    for (let key of Reflect.ownKeys (obj)) {
      if (typeof result[ key ] === 'undefined') {
        result[ key ] = obj[ key ];
      }
    }
    return Immutable (result);
  }
  
  function isImmutable (obj) {
    return obj[ ISIMMUTABLE ] ||
           (typeof obj !== 'object' && typeof obj !== 'function');
  }
  
  Immutable.assign      = assign;
  Immutable.create      = Immutable;
  Immutable.isImmutable = isImmutable;
  return Object.freeze (Immutable);
}) ();

Librerías para estructuras de datos inmutables

Además de esta pequeña utilidad que aquí os mostramos, hay librerías muy completas y bastante populares para gestionar datos inmutables en Javascript. Dos de ellas tienen especial interés:

  • Ramda es una librería que permite gestionar tuberías de ejecución con datos inmutables. Es una librería bastante completa y con una amplia difusión en la comunidad.
  • Immutable es una librería desarrollada por Facebook para gestionar estructuras de datos inmutables de forma muy optimizada, ya que por medio de técnicas de hash maps tries y vector tries evitan tener que copiar grandes cantidades de datos cada vez que creamos una nueva versión de las colecciones.

Os animamos a que descubráis que os pueden aportar estas librerías o a desarrollar vuestras utilidades. Es bastante más sencillo de lo que puede parecer a simple vista.

Conclusiones

Sea cual sea el mecanismo que utilicemos, desde una mera convención en el equipo de desarrollo para no desarrollar o utilizar funciones que muten los datos, pasando por el bloqueo de cambios en los objetos por medio Object.freeze(), hasta las completas librerías de manejo de datos inmutables, es importante comprender y gestionar adecuadamente la inmutabilidad para obtener un código mucho más predecible, fácilmente depurable y, en general, más confiable.

Quizás pensemos que la inmutabilidad sólo es útil en programación funcional, pero no es así, la inmutabilidad se puede utilizar en cualquier estilo/modelo de programación. Quizás la extensa mutabilidad que ofrece Javascript nos haga pensar que es más idiomático utilizar la mutabilidad, pero Javascript también ofrece mecanismos y soluciones para gestionar de forma bastante sencilla la inmutabilidad. Usemos o no los mecanismos que nos ofrece el lenguaje para asegurar la inmutabilidad de los datos, es conveniente conocer como trabajar con este tipo de restricción y decidir luego cuando y cómo la utilizamos.

Novedades

Copiar objetos en Javascript

Copiar objetos en Javascript

Copiar objetos no es algo sencillo, incluso se podría decir que en si mismo no es posible, ya que el concepto «copiar» no entra dentro del paradigma de los objetos. No obstante, por medio de instrucciones como Object.assign() hemos aprendido como obtener objetos con las mismas propiedades, pero está técnica no se puede aplicar a todos los tipos de objetos disponibles en Javascript. Vamos a ver cómo podemos copiar cualquier tipo de objeto…

Descubre los Javascript Array Internals

Descubre los Javascript Array Internals

El Array es una de las estructuras más utilizadas en Javascript y no siempre bien comprendida. Hoy os invitamos a analizar el comportamiento interno de este objeto y descubrir cómo Javascript implementa las diferente acciones con los Array y que operaciones internas se realizan en cada caso.

Web Components: pasado, presente y futuro

Web Components: pasado, presente y futuro

Los Web Components aparecieron en el panorama de desarrollo hace ya bastante tiempo. Desde su presentación se les ha prestado mucha atención, pero lo cierto es que no han sido acogidos de forma generalizada, quizás por la difusión de nuevos y potentes frameworks. Nos preguntamos qué ha pasado con este estándar y, sobre todo, que puede pasar de aquí en adelante con el uso práctico de los componentes web.

Adaptar un componente web para Microsoft Edge

Adaptar un componente web para Microsoft Edge

Si quieres que tus componentes web funcionen sin problemas en Microsoft Edge, es conveniente que conozcas algunas de sus carencias y cómo resolverlas. Edge tiene soporte nativo a algunas de las características clave de los web component, pero hay que utilizar polyfill en otras y hay que tener en cuenta algunas limitaciones. Próximamente Edge utilizará Chromium como base de su desarrollo, pero de momento, es necesario aplicar estas soluciones.

12 pasos para construir un componente web

12 pasos para construir un componente web

Para conocer cómo se desarrolla realmente un componente web no puedes perderte esta descripción paso a paso de la construcción de un componente real y de cierta complejidad. Podrás descubrir cómo, sólo utilizando el estándar, se pueden hacer cosas muy interesantes y completas.

¿Qué pasa con import y los web components?

¿Qué pasa con import y los web components?

Uno de los más controvertidos pilares de los componentes web ha sido el HTML Import. Considerado en estos momentos como una funcionalidad discontinuada, debemos conocer como sacar el máximo partido la instrucción import de Javascipt para gestionar la carga de nuestros componentes.

Template a fondo

Template a fondo

Hay dos formas estándar de crear contenido en un componente de forma flexible: la etiqueta template, que se considera como uno de los pilares de los Web Components y las template string de Javascript, que son una buena alternativa para generar el Shadow DOM con interpolación de datos.

Light DOM a fondo

Light DOM a fondo

El Light DOM es un espacio compartido entre nuestro componente web y el DOM general, que podemos utilizar para insertar contenido o configurar nuestro componente. Es una muy interesante característica que debemos conocer.