En Javascript los objetos son siempre pasados por referencia entre una variable y otra, por lo que no es evidente la forma en la que se pueden copiar objetos y obtener una nueva entidad independiente. Dependiendo de que otros lenguajes de programaci贸n se han utilizado antes, esta caracter铆stica puede parecer m谩s o menos extra帽a, pero en todos los casos, este comportamiento suele provocar problemas y efectos colaterales no deseados, especialmente cuando, por ejemplo, una funci贸n modifica sin avisar un objeto que ha sido pasado c贸mo par谩metro.
Por ejemplo, en este sencillo caso parece que estamos copiando el valor de a
en b
, pero lo que estamos haciendo es obtener otra nueva referencia al mismo objeto. De esta forma, cuando cambiamos a[0]
tambi茅n estamos cambiando b[0]
:
const a = [1,2,3,4,5]; const b = a; a[0] = 'uno'; console.assert(b[0] === 'uno');
En Javascript se pueden 芦copiar禄 objetos, aunque no dispongamos de un operador para ello. Vamos a ver paso a paso c贸mo se hace.
Operador de propagaci贸n (spread operator)
En ES2015 apareci贸 el operador de propagaci贸n o spread operator (en ingl茅s). Este operador facilita mucho la copia del contenido de un Array en otro:
const a = [1,2,3,4,5]; const b = [...a]; console.assert(a !== b);
En ES2018 se incluy贸 este mismo operador de propagaci贸n para los objetos literales y, por lo tanto, podemos con mucha facilidad copiar los valores de los objetos, es decir, las propiedades de uno en otro:
const x = {a: 1, b: 2, c: 3}; const y = {...x}; console.assert(x !== y);
En ambos casos, conseguimos lo que est谩bamos buscando: copiar los valores del objeto. Esta operaci贸n se puede realizar por otros medios, como el m茅todo .slice()
de los Array
o el m茅todo Object.assign()
, pero creo que todos coincidiremos que el operador de propagaci贸n es muy sencillo de utilizar y bastante elegante.
Copia superficial vs. copia en profundidad
Cuando copiamos un objeto o una matriz, estamos haciendo una copia superficial de los valores del objeto, es decir, s贸lo estamos duplicando los valores del primer nivel de las propiedades del propio objeto y mantenemos intactos los objetos que pudieran existir a otros niveles de profundidad como objetos dentro de las propiedades.
const x = {a : {a : 1}, b : {b : 2}, c : {c : 3}}; const y = {...x}; console.assert (x.a !== y.a); // Assertion failed
La soluci贸n es sencilla, para poder hacer es copiar objetos en profundidad debemos crear una peque帽a funci贸n que de forma recursiva recorra los elementos del objeto y aplique el m茅todo de copia que estemos utilizando:
function copy (obj) { let result; if (obj instanceof Array) { result = [ ...obj ]; } else if (typeof obj === 'object') { result = {...obj} } else { return obj; } for (let prop of Reflect.ownKeys (result)) { result[ prop ] = copy (result[ prop ]); } return result; } const a = [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]; const b = copy (a); console.assert (a[ 0 ] !== b[ 0 ]); const x = {a : {a : 1}, b : {b : 2}, c : {c : 3}}; const y = copy (x); console.assert (x.a !== y.a);
Subclases que heredan de las clases predefinidas
Al hacer este tipo de funciones se nos suele olvidar que en ES2015 en adelante es posible crear clases derivadas de objetos predefinidos y que, por ejemplo, podemos crear una clase MyArray
que herede de Array
:
class MyArray extends Array { intersection(arr) { return new MyArray(...this.filter(value => arr.includes(value))); } }
Si aplicamos la funci贸n copy()
que hicimos en la secci贸n anterior a este tipo de objeto, obtendremos un objeto Array
y no un objeto MyArray
:
const x = copy(a); console.assert(x instanceof MyArray); // Assertion failed
La soluci贸n es muy sencilla, s贸lo tenemos que cambiar de estrategia para hacer la creaci贸n del nuevo objeto utilizando el mismo constructor y realizar copia de los valores de sus propiedades. La funci贸n queda de esta forma:
function copy (obj) { let result; if (typeof obj === 'object') { result = new obj.constructor(); } else { return obj; } for (let prop of Reflect.ownKeys (obj)) { result[ prop ] = copy (obj[ prop ]); } return result; }
Con esta nueva funci贸n se permite copiar objetos Array
, Object
y las clases que hereden de ellos:
class MyArray extends Array { intersection(arr) { return new MyArray(...this.filter(value => arr.includes(value))); } } class MyObject extends Object { constructor() { super(); this.class = 'MyModel'; } } const a = new MyArray(1,2,3,4,5); const b = copy(a); console.assert(b instanceof MyArray); console.assert(a != b); const x = new MyObject(); const y = copy(x); console.assert(y instanceof MyObject) console.assert(a != b)
El caso especial de null
Aunque el c贸digo anterior tiene muy buen aspecto, lo cierto es que escode un problema importante: no funciona si el objeto es null
. Este valor es un objeto, pero carece de constructor, por lo que la llamada a obj.constructor()
lanzar谩 una excepci贸n.
const x = {n : null}; const y = copy (x); // TypeError: Cannot read property 'constructor' of null
Bueno, s贸lo tenemos que incluir unas pocas l铆neas para controlar este caso:
function copy (obj) { let result; if (obj === null) { return obj; } if (typeof obj === 'object') { result = new obj.constructor (); } else { return obj; } for (let prop of Reflect.ownKeys (obj)) { result[ prop ] = copy (obj[ prop ]); } return result; }
El objeto Date
Seguramente alguno de vosotros se habr铆a dado cuenta, nos hemos olvidado del objeto Date. Las fechas en Javascript son un tipo de objeto y, por lo tanto, debemos encontrar una forma correcta para copiar objetos de tipo fecha y no pasarlos por valor al nuevo objeto. En nuestro c贸digo, aunque se llama a su constructor, no estamos estableciendo la fecha adecuada, ya que el valor de la fecha se debe incluir en la llamada al constructor y no basta con copiar el valor de las propiedades para igualar el valor interno de la fecha.
Nuevamente son unas pocas l铆neas m谩s de c贸digo las que tenemos que incluir para controlar este nuevo caso, comprobando que estamos ante un objeto que hereda de Date
:
function copy (obj) { let result; if (obj === null) { return obj; } if (obj instanceof Date) { result = new obj.constructor (obj.valueOf()); } else if (typeof obj === 'object') { result = new obj.constructor (); } else { return obj; } for (let prop of Reflect.ownKeys (obj)) { result[ prop ] = copy (obj[ prop ]); } return result; }
隆Alto! 驴pero cuantos tipos de objetos hay en Javascript?
Ahora que nos hemos percatado de que nos hab铆amos olvidado del objeto Date
, podemos preguntarnos qu茅 otros tipos de objetos nos hemos olvidado y que pueden ser susceptibles de ser copiados en Javascript, pueden estar contenidos en una matriz, en un objeto o simplemente necesitamos clonarlos. Para hacer una funci贸n consistente para copiar objetos deber铆amos tener una respuesta razonable para todos los casos y no s贸lo para unos pocos.
Lo cierto es que la lista de tipos de objetos que podemos instanciar es bastante grande, mucho m谩s de lo que solemos pensar. De forma resumida incluimos aqu铆 esta referencia:
- Objectos b谩sicos:
- Recubrimiento de los tipos primitivos, normalmente no se utilizan con
new
,
pero si se hace, se crea un objeto que recubre el valor primitivo: - Estructuras de datos:
Map
, es una colecci贸n de pares clave/valor donde cualquier tipo puede usarse como clave o como valor..Set
, permite almacenar valores 煤nicos de cualquier tipo, ya sean valores primitivos u objetos.WeakMap
, es una colecci贸n de pares clave/valor en el que las claves son siempre objetos y est谩n d茅bilmente referenciados.WeakSet
, permite almacenar objetos levemente referenciados.
- Errores, son objetos de diferente tipo, aunque todos heredan de
Error
:Error
, error gen茅rico.EvalError
, existe por compatibilidad hacia atr谩sRangeError
, error que indica que un valor no est谩 dentro de rango aceptado.ReferenceError
, error que se produce cuando se usa una variable no creada.SyntaxError
, se produce cuando hay un error de sintaxis.TypeError
, se lanza cuando el tipo no es el esperado.URIError
, se lanza cuando se intenta utilizar una URI mal formada.
- Funciones:
Function
, rara vez se utiliza connew
y casi siempre se utiliza una sintaxis comofunction () {}
o() => {}
.GeneratorFunction
, no se puede utilizar connew
, siempre se utiliza una sintaxisfunction * () {}
.AsyncFunction
, no se puede utilizar connew
, siempre se utiliza una sintaxisasync function () {}
.
- Expresiones regulares, para gestionar patrones en cadenas de texto:
RegExp
, se puede crear tambi茅n con la expresi贸n literal/ /
.
- Captura de operaciones con los objetos:
Proxy
, devuelve un objeto observado por medio de un conjunto de manejadores.
- Control de ejecuci贸n:
Promise
, devuelve un objeto para manejar la ejecuci贸n as铆ncrona de una funci贸n.Array Iterator
, no se puede crear directamente, se utiliza para iterar unArray
.AsyncGenerator
, no se puede crear directamente, se devuelve por una funci贸n as铆ncrona y generadora.Generator
, no se puede crear directamente, se devuelve por las funciones generadoras.String Iterator
, no se puede crear directamente, se devuelve al iterar una cadena de texto.Async Iterator
, no se puede crear directamente, es utilizado en los buclesfor wait of
.Map Iterator
, no se puede crear directamente, se devuelve al iterar unMap
.Set Iterator
, no se puede crear directamente, se devuelve al iterar unSet
.RegExp String Iterator
, no se puede crear directamente, se devuelve al ejecutar el m茅todo.matchAll()
.
- Manejo de datos binarios:
ArrayBuffer
, crea un buffer de tama帽o fijo para el manejo de datos binarios.SharedArrayBuffer
, crea un buffer de tama帽o fijo compartido y no eliminable para el manejo de datos binarios.DataView
, permite manejar unArrayBuffer
oSharedArrayBuffer
a bajo nivel.TypedArray
, no se puede crear directamente, de 茅l heredan los siguientes objetos:BigInt64Array
, manejo del buffer como enteros de 64 bits.BigUint64Array
, manejo del buffer como enteros de 64 bits sin signo.Float32Array
, manejo del buffer como n煤mero de coma flotante de 32 bits.Float64Array
, manejo del buffer como n煤mero de coma flotante de 64 bits.Int8Array
, manejo del buffer como enteros de 8 bits.Int16Array
, manejo del buffer como enteros de 16 bits.Int32Array
, manejo del buffer como enteros de 32 bits.Uint8Array
, manejo del buffer como enteros de 8 bits sin signo.Uint8ClampedArray
, manejo del buffer como enteros de 8 bits sin signo con valores entre 0 y 255.Uint16Array
, manejo del buffer como enteros de 16 bits sin signo.Uint32Array
, manejo del buffer como enteros de 32 bits sin signo.
- Objectos globales, de los que no se puede crear una nueva instancia:
Atomics
, contiene m茅todos para el manejo de unShareArrayBuffer
.JSON
, contiene m茅todos para la interpreaci贸n y creaci贸n de cadenas de texto con formato JSON.Math
, contiene m茅todos y constantes matem谩ticas.Reflect
, contiene m茅todos para reproducir operaciones interceptadas, habitualmente en unProxy
.globalThis
, corresponde conwindow
en los navegaores y conroot
en Node y es donde residen las variables globales.null
, objeto con valornull
Intl
, contiene los objetos del ECMAScript Internationalization API.
A estas clases de objetos se deber铆an a帽adir los tipos de objetos que creemos nosotros con class
, ya sea heredando de Object
o de cualquiera de los constructores predefinidos. La naturaleza y dise帽o de estas clases puede ser muy variado y hay que tener en cuenta las caracter铆sticas concretas de cada uno.
Tambi茅n es importante tener en cuenta dos situaciones peculiares que se pueden producir al utilizar Object.create()
Object.create({ })
, devuelve un objeto cuyo constructor esObject
pero cuyo prototipo es el objeto pasado como par谩metro.Object.create(null)
, devuelve un objeto que no hereda deObject
y que tiene como prototiponull
, pero carece de constructor.
Estos objetos tienen un comportamiento especial y debemos conocerlo.
Tambi茅n tenemos que recordar que los objetos pueden hacer referencia a si mismos, es decir, tener referencias circulares. Si no recordamos est谩 caracter铆stica seguramente tengamos algunos problemas.
Por ultimo, tenemos que recordar que las propiedades de los objetos se pueden definir y configurar con Object.defineProperty()
, indicando si son enumerables o no, si son de s贸lo lectura o est谩n gestionadas con un m茅todo get
y un m茅todo set()
, etc. Cuando trabajemos con las propiedades es conveniente que utilicemos sus descriptores y no s贸lo los creemos directamente.
Ahora tenemos que volver a preguntarnos, 驴c贸mo es posible copiar objetos de todos estos tipos teniendo en cuenta sus diferentes peculiaridades y caracter铆sticas?
Una funci贸n para copiarlos a todos
Aunque parezca dif铆cil, lo cierto es que podemos crear con bastante facilidad una funci贸n para copiar objetos correctamente independientemente del tipo que sean y de las caracter铆sticas de que dispongan. Vamos a ver c贸mo.
En primer lugar, tenemos que aclarar que algunos objetos no tiene mucho sentido que sean copiados. Por ejemplo, copiar funciones (que son objetos) no parece tener mucha utilidad. Podr铆amos obtener su c贸digo y volverlo a evaluar, pero no es algo que tenga mucho sentido en la pr谩ctica. Tampoco tiene sentido que copiemos los objetos que se utilizan para gestionar el control de ejecuci贸n como Promise
, los objetos que manejan los iteradores o los devueltos por las funciones generadoras. El estado de estos objetos debe mantenerse de forma 煤nica y obtener un duplicado s贸lo puede producir problemas. Por 煤ltimo, tenemos que comprender que no se pueden copiar objetos de tipo WeakMap
y WeakSet
, ya que carecemos de mecanismos para recorrer su contenido y s贸lo podemos acceder a sus elementos si conocemos previamente las claves que contienen.
Tambi茅n tenemos que tener en cuenta que copiar objetos de clases personalizadas puede llegar a ser bastante complejo, sobre todo si establecen constructores que reciben los datos por par谩metro y no reflejan los mismos en propiedades p煤blicas que podamos copiar. Esto ocurre tanto en clases que heredan de Object
como las que heredan de otros constructores. Para estos casos vamos a crear un s铆mbolo bien conocido para implementar en nuestras clases un m茅todo que permita la copia de los objetos. Si la clase implementa un m茅todo con el s铆mbolo, se utilizar谩 para hacer la copia del objeto. No es una soluci贸n universal, pero al menos podemos aplicarla en nuestras clases.
Ahora que tenemos claro que cosas no vamos a copiar y que limitaciones tenemos, podemos mostrar el c贸digo de esta funci贸n que puede copiar objetos de cualquier tipo:
const copy = (function () { const TypedArray = Object.getPrototypeOf (Int32Array); const hasNode = typeof Node !== 'undefined'; const hasSharedArrayBuffer = typeof SharedArrayBuffer !== 'undefined'; const ignoreTypes = [ 'Array Iterator', 'AsyncGenerator', 'Generator', 'String Iterator', 'Async Iterator', 'Map Iterator', 'Set Iterator', 'RegExp String Iterator', 'Promise', 'WeakMap', 'WeakSet' ]; const stack = new Map(); // Stack for circular objects function copy (obj) { if (typeof obj !== 'object') { return obj; } if (obj === null || // null or ignore object type ignoreTypes.includes (obj[ Symbol.toStringTag ])) { return obj; } if (stack.has(obj)) { // Check if it's a circular object return stack.get(obj); } let result; let proto = Object.getPrototypeOf (obj); if (obj[ copy.symbol ]) { // Well kwon symbol for copy result = obj[ copy.symbol ] (); } else if (proto === Object.prototype) { // Simple object result = {}; } else if (proto === null) { // Create.object(null) result = Object.create (null); } else if (obj instanceof Date || // Date, Boolean, String, Number obj instanceof Boolean || obj instanceof Number || obj instanceof String) { result = new (obj.constructor) (obj.valueOf ()); } else if (obj instanceof Map) { // Map result = new (obj.constructor) (); for (let key of obj.keys ()) { result.set (key, copy (obj.get (key))); } } else if (obj instanceof Set) { // Set result = new (obj.constructor) (); for (let value of obj.values ()) { result.add (copy (value)); } } else if (obj instanceof ArrayBuffer || // ArrayBuffer, SharedArrayBuffer (hasSharedArrayBuffer && obj instanceof SharedArrayBuffer)) { result = obj.slice (0); } else if (obj instanceof DataView) { // DataView result = new (obj.constructor) (obj.buffer.slice (0)); } else if (obj instanceof TypedArray) { // All typed array result = new obj.constructor (obj); } else if (obj instanceof RegExp) { // RegExp result = new (obj.constructor) (obj.source, obj.flags || obj.options); } else if (obj instanceof Error) { // Error and derived objects result = new (obj.constructor) (obj.toString ()); if (obj.stack) { result.stack = obj.stack; // Firefox fix } } else if (hasNode && obj instanceof Node) { // HTML Node result = obj.cloneNode (true); } else if (obj.constructor === Object) { // Custom propotype result = Object.create(proto); } else { // Array or other objects result = new (obj.constructor) (); } stack.set(obj, result); // Update stack for circular objects for (let key of Reflect.ownKeys (obj)) { // Properties const descrOrigin = Object.getOwnPropertyDescriptor (obj, key); const descrResult = Object.getOwnPropertyDescriptor (result, key); if (!descrResult || descrResult.configurable) { if (descrOrigin.set || descrOrigin.set) { // descriptor with getter and setter Object.defineProperty ( result, key, Object.assign (descrOrigin, { set : (descrOrigin.set ? descrOrigin.set.bind (result) : undefined), get : (descrOrigin.get ? descrOrigin.get.bind (result) : undefined) }) ); result[ key ] = copy (obj[ key ]); } else { // descriptor with value Object.defineProperty ( result, key, Object.assign (descrOrigin, {value : copy (obj[ key ])}) ); } } } return result; } copy.symbol = Symbol ('method copy'); return copy; })();
Son unas pocas l铆neas de c贸digo, y seguramente se explican por si solas, pero vamos a explicar que estamos haciendo paso a paso:
- Se crea una constante
copy
con el retorno de una funci贸n de ejecuci贸n inmediata. - Se crean unas constantes:
- Obtenemos el constructor de
Int32Array
para poder identificar todos los Typed Array - Comprobamos si
Node
est谩 disponible (s贸lo lo estar谩 en los navegadores) - Comprobamos si
SharedArrayBuffer
est谩 disponible, ya no todas las implementaciones de Javascript lo han incorporado - Creamos una lista de descriptores de objetos que vamos a ignorar.
- Creamos un objeto
Map
donde vamos a guardar los objetos que ya hemos procesado para gestionar adecuadamente las referencias circulares.
- Obtenemos el constructor de
- Se define la funci贸n
copy
:- Si no es un objeto, entonces ser铆a un valor primitivo o una funci贸n, se devuelve el valor original
- Si es
null
o uno de los tipos que no se van a copiar, se devuelve el valor original - Si ya ha sido proceso est谩 dentro del objeto
Map
y para evitar procesarlo en bucle infinito devolvemos el objeto que se incluy贸 en esa pila - Si el objeto tiene un m茅todo definido con
copy.simbol
, se llama a ese m茅todo para obtener una copia del objeto - Si el prototipo del objeto es el mismo que Object, entonces es un objeto simple que podemos crear con el literal
{}
- Si el prototipo del objeto es
null
, entonces el objeto se cre贸 conObject.create(null)
- Si el objeto est谩 heredando de
Date
,Boolean
,String
oNumber
, se llama a su constructor pasando el valor interno obtenido con.valueOf()
- Si el objeto est谩 heredando de
Map
, se llama al constructor y se insertan los valores con el m茅todo.set()
- Si el objeto est谩 heredando de
Set
, se llama al constructor y se insertan los valores con el m茅todo.add()
- Si el objeto est谩 heredando de
ArrayBuffer
oSharedArrayBuffer
se obtiene una copia por medio del m茅todo.slice()
- Si el objeto est谩 heredando de
DataView
, se llama al constructor pasando como par谩metro una copia del buffer que est谩 manejando - Si el objeto est谩 heredando de
TypeArray
, se llama al constructor pasando como par谩metro el propio objeto - Si el objeto est谩 heredando de
RegExp
, se llama al constructor pasando la expresi贸n regular obtenido de la propiedad.source
y los modificadores por medio de la propiedad.flags
u.options
ya que dependiendo de la implementaci贸n est谩 en una propiedad o en otra - Si el objeto est谩 heredando de
Error
, se llama al constructor pasando el mensaje como par谩metro y copiando la propiedad.stack
en Firefox - Si el objeto est谩 heredando de
Node
, estamos en un navegador y copiamos el elemento con el m茅todo.cloneNode()
- Si el constructor es
Object
, pero hemos llegado hasta aqu铆, quiere decir que el prototipo no es el delObject
, por lo que es el caso especial de creaci贸n conObject.create({ })
- En cualquier otro caso, es un
Array
o un objeto creado a partir de una clase - Para todos los objetos, se recorren sus propiedades propias, enumerables y no enumerables, y
- Se obtiene el descriptor de la propiedad para el objeto original y el que hemos creado
- Si en el objeto creado se permite la modificaci贸n de la propiedad
- Si el descriptor es de tipo setter/getter
- se crea con esa estructura en el objeto
- se asigna el valor llamando de forma recursiva a
copy()
- Si el descriptor es de tipo
value
, se llama asigna el valor聽de forma recursiva acopy()
- Si el descriptor es de tipo setter/getter
- Se retorna el nuevo objeto
- Creamos un
Symbol
para poder utilizarlo como nombre del m茅todo de copia en nuestras clases. - Se retorna la funci贸n
copy
Conclusiones
Cuando empezamos parec铆a que todo ser铆a muy sencillo, que un operador y algunas l铆neas en una peque帽a funci贸n nos iban a resolver todos nuestros problemas a la hora de copiar objetos. Lo cierto es que en muchos casos esta aproximaci贸n simplificada cubre la mayor铆a de las situaciones y puede ser razonable su uso. Intencionadamente hemos querido empezar de esta forma, c贸mo suelen iniciarse el desarrollo de este tipo de funciones, partiendo de unos pocos casos sencillos.
En la mayor铆a de las ocasiones vamos a darnos cuenta r谩pidamente que tenemos que dar respuesta a m谩s situaciones de las que hab铆amos pensado inicialmente, especialmente si lo que estamos es buscando una funci贸n verdaderamente robusta, que de respuesta a todos los casos. El Javascript moderno ofrece una buena cantidad de tipos de objetos y algunos casos bastante peculiares que hacen que los objetos se comporten de forma diferente. No es demasiado complicado dar respuesta a todos ellos. Quiz谩s pueda resultar un poco tedioso analizar caso por caso, pero existen respuestas para todos los casos, s贸lo tenemos que aplicarnos un poco para ser exhaustivos.
Si vais a abordar alguna funci贸n global para el manejo de objetos os recomendamos que teng谩is a mano una lista de objetos posibles en Javascript y comprob茅is como funciona vuestro c贸digo en cada caso. Os podr茅is sorprender en bastantes situaciones de lo poco preparados que estamos para responder a a un simple objeto Date
,聽 a un objeto Map
o a una referencia circular. No hay que agobiarse, s贸lo tener en cuenta que el lenguaje ha crecido y tiene un mayor n煤mero de tipos de objetos a los que nos tenemos que enfrentar. Con un poco de pr谩ctica conseguiremos dar respuesta general a todos los casos que se nos presenten.
Novedades
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
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
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
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.