informatica:programacion:cursos:programacion_avanzada_javascript:proxies
Diferencias
Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa | ||
| informatica:programacion:cursos:programacion_avanzada_javascript:proxies [2024/10/28 12:01] – tempwin | informatica:programacion:cursos:programacion_avanzada_javascript:proxies [2024/10/30 16:03] (actual) – [Recursos] tempwin | ||
|---|---|---|---|
| Línea 25: | Línea 25: | ||
| ===== Creación de proxies ===== | ===== Creación de proxies ===== | ||
| + | Crear un proxy no es muy complicado. Lo primero es tener presente que, dado que un proxy intercepta operaciones sobre un objeto, vamos a necesitar lógicamente un objeto al que interceptar. Este objeto no tiene que tener nada especial: | ||
| + | |||
| + | <code javascript> | ||
| + | let obj = { | ||
| + | get value() { return 42;} | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Hemos definido un objeto '' | ||
| + | |||
| + | <code javascript> | ||
| + | let proxy = new Proxy(obj, | ||
| + | { | ||
| + | get(target, key, receiver) { | ||
| + | console.log(`Prop ${key} was called`); | ||
| + | return target[key]; | ||
| + | } | ||
| + | }); | ||
| + | let v = proxy.value; | ||
| + | console.log(v); | ||
| + | </ | ||
| + | |||
| + | Vamos a diseccionar el código. **Para crear un proxy debemos crear un objeto del tipo '' | ||
| + | |||
| + | * **El objeto cuyas operaciones queremos interceptar** | ||
| + | * **Un objeto que indica qué acciones queremos interceptar** y qué queremos hacer cuando las interceptemos. | ||
| + | |||
| + | En este caso el objeto pasado (que se suele denominar // | ||
| + | |||
| + | Luego en la intercepción nos limitamos a imprimir por la consola cuál es la propiedad accedida y devolvemos el valor correcto de dicha propiedad (podríamos en efecto devolver cualquier otra cosa si quisiéramos). | ||
| + | |||
| + | Finalmente llamamos al //getter// '' | ||
| + | |||
| + | <WRAP center round info 60%> | ||
| + | Es importante observar que el //getter// ('' | ||
| + | </ | ||
| + | |||
| + | Enseguida lo vamos a ver en la práctica y se entenderá mucho mejor. Pero antes analicemos las posibles intercepciones en los proxies. | ||
| ===== Intercepciones básicas ===== | ===== Intercepciones básicas ===== | ||
| + | |||
| + | Vamos a ver qué podemos interceptar con un proxy. Las intercepciones básicas son llamadas a //getters// de propiedad, a //setters// y a métodos. | ||
| + | |||
| + | ==== Getters de propiedad ==== | ||
| + | |||
| + | Ya lo hemos visto en la lección anterior: basta con definir un método '' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Así p. ej. podríamos crear un proxy que crease una propiedad " | ||
| + | |||
| + | <code javascript> | ||
| + | let obj = {v: 42}; | ||
| + | |||
| + | let proxy = new Proxy(obj, { | ||
| + | get (target, key, receiver) { | ||
| + | if (key === " | ||
| + | | ||
| + | } | ||
| + | else { | ||
| + | | ||
| + | } | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | let str = proxy.json; | ||
| + | </ | ||
| + | |||
| + | ==== Setters de propiedad ==== | ||
| + | |||
| + | Para interceptar un //setter// de una propiedad el //handler// debe definir un método '' | ||
| + | |||
| + | Vamos a crear un proxy que obligue a que todas las propiedades de un objeto tengan un valor de tipo cadena: | ||
| + | |||
| + | <code javascript> | ||
| + | let obj = {v: "una cadena" | ||
| + | let proxy = new Proxy(obj, { | ||
| + | set (target, key, receiver) { | ||
| + | if (typeof(receiver) === " | ||
| + | | ||
| + | } | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | proxy.v = 42; | ||
| + | proxy.v; | ||
| + | proxy.v = "ahora sí"; | ||
| + | proxy.v; | ||
| + | </ | ||
| + | |||
| + | El uso de proxies es muy sencillo y nos da una potencia brutal, permitiéndonos realizar tareas que sin ellos serían mucho más difíciles. | ||
| + | |||
| + | ==== Llamadas a métodos ==== | ||
| + | |||
| + | Para llamar a métodos se usa también el método '' | ||
| + | |||
| + | Cuando tenemos un código tipo '' | ||
| + | |||
| + | <code javascript> | ||
| + | var obj = { | ||
| + | v: 42, | ||
| + | inc(value) {this.v += value; return this.v; } | ||
| + | } | ||
| + | |||
| + | let proxy = new Proxy(obj, { | ||
| + | get (target, key, receiver) { | ||
| + | var prop = target[key]; | ||
| + | if (typeof(prop) === " | ||
| + | return function(...args) { | ||
| + | let result = prop.apply(this, | ||
| + | console.log(`${key}(${JSON.stringify(args)}) -> ${result != undefined ? JSON.stringify(result) : undefined}`); | ||
| + | return result; | ||
| + | } | ||
| + | } | ||
| + | else return target[key]; | ||
| + | } | ||
| + | }); | ||
| + | </ | ||
| + | |||
| + | Este código intercepta todas las llamadas a todos métodos y las imprime por la consola. Observa el uso de '' | ||
| + | |||
| + | De este modo cuando hacemos '' | ||
| + | |||
| + | * Se llama al método '' | ||
| + | * Este método obtiene el valor de la propiedad indicada ('' | ||
| + | * Si la propiedad es una función (en este caso lo es), devuelve otra función que llama a la función del objeto original (usando '' | ||
| + | * Si la propiedad llamada no fuese una función (p. ej. hiciéramos '' | ||
| ===== DEMO: Interceptando un getter a propiedad ===== | ===== DEMO: Interceptando un getter a propiedad ===== | ||
| + | Lo importante de los //proxies// es entender que envuelven un objeto y luego nos permiten interceptar llamadas a las propiedades y métodos del objeto y terminar delegando esta llamada a la propiedad real del objeto o devolver cualquier otra cosa a quien hace la llamada. | ||
| + | |||
| + | Partimos de un objeto sencillo: | ||
| + | |||
| + | <code javascript> | ||
| + | var obj = { | ||
| + | prop: 42 | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Ahora podemos tener una función así: | ||
| + | |||
| + | <code javascript> | ||
| + | function foo(p) { | ||
| + | console.log(p.prop) | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Al usarla: | ||
| + | |||
| + | <code javascript> | ||
| + | foo(obj); // 42 | ||
| + | </ | ||
| + | |||
| + | Vamos a crear un proxy para envolver el objeto: | ||
| + | |||
| + | <code javascript> | ||
| + | let proxy = new Proxy(obj, { | ||
| + | get(target, key, receiver) { | ||
| + | console.log(target, | ||
| + | } | ||
| + | }) | ||
| + | </ | ||
| + | |||
| + | Ahora a la función debemos pasarle el proxy, no el objeto original: | ||
| + | |||
| + | <code javascript> | ||
| + | foo(proxy); // Object { prop: 42 } prop Object { prop: 42 } | ||
| + | </ | ||
| + | |||
| + | Modificamos el proxy: | ||
| + | |||
| + | <code javascript> | ||
| + | let proxy = new Proxy(obj, { | ||
| + | get(target, key, receiver) { | ||
| + | if (key == " | ||
| + | return target[key]; | ||
| + | } else { | ||
| + | return NaN; | ||
| + | } | ||
| + | | ||
| + | } | ||
| + | }) | ||
| + | </ | ||
| + | |||
| + | <code javascript> | ||
| + | foo(proxy); // 42 | ||
| + | </ | ||
| + | |||
| + | Si modificamos la función '' | ||
| + | |||
| + | <code javascript> | ||
| + | function foo(p) { | ||
| + | console.log(p.prop) | ||
| + | console.log(p.cualquierCosa); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Y ejecutamos: | ||
| + | |||
| + | <code javascript> | ||
| + | foo(proxy); // 42 | ||
| + | // NaN | ||
| + | </ | ||
| + | |||
| + | Hemos visto cómo el proxy nos ha permitido envolver de manera sencilla un objeto y expandir su comportamiento sin necesidad de tocar el objeto original. | ||
| ===== Ejercicio propuesto ===== | ===== Ejercicio propuesto ===== | ||
| + | Se propone el siguiente ejercicio: Usa proxies para ocultar automáticamente de un objeto todas aquellas propiedades que empiecen por un subrayado. Es decir, dado un objeto: | ||
| + | |||
| + | <code javascript> | ||
| + | let obj = { | ||
| + | _pr: 10, | ||
| + | pu: 20 | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | Se quiere que la propiedad '' | ||
| + | |||
| + | Tienes la solución en el siguiente apartado y en el fichero '' | ||
| + | |||
| + | ==== DEMO: Solución al ejercicio de ocultar variables privadas ==== | ||
| + | |||
| + | Partamos de: | ||
| + | |||
| + | <code javascript> | ||
| + | var obj = { | ||
| + | _v: 10, | ||
| + | set v(value) { | ||
| + | // Solo para valores positivos | ||
| + | if (value > 0) { | ||
| + | this._v = value; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Probamos: | ||
| + | |||
| + | <code javascript> | ||
| + | obj.v = 1200; | ||
| + | obj._v = -10; | ||
| + | |||
| + | console.log(obj._v); | ||
| + | </ | ||
| + | |||
| + | Ahora, vamos a usar un proxy para ocultar una variable privada y que no podamos ejecutar lo anterior. | ||
| + | |||
| + | <code javascript> | ||
| + | const hidePrivates = function(obj) { | ||
| + | const handler = { | ||
| + | get (target, key, receiver) { | ||
| + | if (key.substr(0, | ||
| + | return undefined; | ||
| + | } else { | ||
| + | return target[key]; | ||
| + | } | ||
| + | }, | ||
| + | set (target, key, receiver) { | ||
| + | if (key.substr(0, | ||
| + | target[key] = receiver; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | | ||
| + | return new Proxy(obj, handler); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Vamos a usarlo: | ||
| + | |||
| + | <code javascript> | ||
| + | let test = hidePrivates(obj); | ||
| + | |||
| + | console.log(test._v); | ||
| + | test.v = 1200; | ||
| + | test._v = 100; | ||
| + | |||
| + | console.log(obj._v); | ||
| + | </ | ||
| ===== Proxies a objetos de tipo función ===== | ===== Proxies a objetos de tipo función ===== | ||
| + | Ya hemos visto cómo con el método '' | ||
| + | |||
| + | Y es que, en JavaScript, las funciones son un tipo más de objeto, así que crear un proxy a una función no debe resultarte sorprendente. | ||
| + | |||
| + | Con un proxy a una función podemos interceptar todas las llamadas a dicha función y ejecutar código antes y/o después de ejecutar la función envuelta por el proxy (aunque también podríamos optar por ni tan siquiera ejecutarla y devolver cualquier otro valor). | ||
| + | |||
| + | Para crear un proxy a función en el handler se define el método apply. Este método recibe los siguientes parámetros: | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Veamos un ejemplo: | ||
| + | |||
| + | <code javascript> | ||
| + | let foo = function(...values) { | ||
| + | let result = 0; | ||
| + | values.forEach(s => result += s); | ||
| + | console.log(' | ||
| + | } | ||
| + | |||
| + | let proxy = new Proxy(foo, { | ||
| + | apply(target, | ||
| + | let args = argumentsList.map(arg => typeof(arg) === " | ||
| + | return target.apply(thisArg, | ||
| + | } | ||
| + | }); | ||
| + | |||
| + | proxy(10, | ||
| + | </ | ||
| + | |||
| + | El proxy intercepta todas las llamadas a '' | ||
| + | |||
| + | ==== Interceptando call, apply y bind ==== | ||
| + | |||
| + | El uso de '' | ||
| + | |||
| + | <code javascript> | ||
| + | proxy.apply([10], | ||
| + | proxy.call([20], | ||
| + | </ | ||
| + | |||
| + | En ambos casos se intercepta la llamada (se ejecuta el método '' | ||
| + | |||
| + | Del mismo modo dado un proxy a una función, si usamos '' | ||
| + | |||
| + | <code javascript> | ||
| + | let proxy2 = proxy.bind(" | ||
| + | proxy(10, 20, " | ||
| + | </ | ||
| + | |||
| + | En resumen, con los proxies podemos interceptar llamadas a funciones de forma sencilla. | ||
| + | |||
| + | Las posibilidades son muy amplias: validación de precondiciones y/o postcondiciones, | ||
| + | |||
| + | Vamos a ver a continuación un caso práctico de uso. | ||
| ===== DEMO: Creando un " | ===== DEMO: Creando un " | ||
| + | Vamos a tener un objeto que nos permitirá generar proxies de objetos y poder congelar el objeto, que las llamadas a los //setters// no sean válidas. | ||
| + | |||
| + | Empezaremos creando una clase que utilizaremos para obtener los proxies de un determinado objeto: | ||
| + | |||
| + | <code javascript> | ||
| + | // Utilizaremos un WeakMap para guardar las variables privadas | ||
| + | // de los objetos de tipo Freezer | ||
| + | const _privates = new WeakMap(); | ||
| + | |||
| + | class Freezer { | ||
| + | |||
| + | constructor(obj) { | ||
| + | let pr = {}; | ||
| + | | ||
| + | | ||
| + | | ||
| + | } | ||
| + | | ||
| + | // Método para " | ||
| + | frozen(value) { | ||
| + | let pr = _privates.get(this); | ||
| + | pr.frozen = value ? true : false; | ||
| + | } | ||
| + | | ||
| + | get value() { | ||
| + | let pr = _privates.get(this); | ||
| + | return pr.proxy; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Vamos a utilizarla: | ||
| + | |||
| + | <code javascript> | ||
| + | var obj = {v: 42}; | ||
| + | |||
| + | var freezer = new Freezer(obj); | ||
| + | var proxy = freezer.value(); | ||
| + | |||
| + | obj.v = 100; | ||
| + | proxy; // Object { v: 100 } | ||
| + | </ | ||
| + | |||
| + | Ahora tendremos que añadir la funcionalidad para congelar el objeto. | ||
| ===== DEMO: Creando un " | ===== DEMO: Creando un " | ||
| + | <code javascript> | ||
| + | // Utilizaremos un WeakMap para guardar las variables privadas | ||
| + | // de los objetos de tipo Freezer | ||
| + | const _privates = new WeakMap(); | ||
| + | |||
| + | const createConfig = function(pr) { | ||
| + | return { | ||
| + | set (target, key, receiver) { | ||
| + | var prop = target[key]; | ||
| + | if (pr.frozen) { | ||
| + | return prop. | ||
| + | } else { | ||
| + | target[key] = receiver; | ||
| + | return receiver; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class Freezer { | ||
| + | |||
| + | constructor(obj) { | ||
| + | let pr = {}; | ||
| + | | ||
| + | | ||
| + | | ||
| + | } | ||
| + | |||
| + | // Método para " | ||
| + | frozen(value) { | ||
| + | let pr = _privates.get(this); | ||
| + | pr.frozen = value ? true : false; | ||
| + | } | ||
| + | |||
| + | get value() { | ||
| + | let pr = _privates.get(this); | ||
| + | return pr.proxy; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Probamos: | ||
| + | |||
| + | <code javascript> | ||
| + | var obj = {v:42}; | ||
| + | |||
| + | var freezer = new Freezer(obj); | ||
| + | var proxy = freezer.value; | ||
| + | |||
| + | proxy; // Object { v: 42 } | ||
| + | |||
| + | // Congelamos el objeto: | ||
| + | freezer.frozen(true); | ||
| + | |||
| + | // Comprobemos que realmente está congelado: | ||
| + | |||
| + | prop.v = 100; | ||
| + | |||
| + | proxy.v; // 42 | ||
| + | |||
| + | // Para descongelarlo: | ||
| + | freezer.frozen(false); | ||
| + | |||
| + | prop.v = 200; | ||
| + | proxy.v; // 200 | ||
| + | </ | ||
| ===== DEMO: Creando un " | ===== DEMO: Creando un " | ||
| + | Después de lo anterior, vamos a agregar un método ficticio al proxy que llamándolo nos permita congelar el propio objeto. | ||
| + | |||
| + | Queremos lograr hacer algo como '' | ||
| + | |||
| + | Vamos a ello: | ||
| + | |||
| + | <code javascript> | ||
| + | // Utilizaremos un WeakMap para guardar las variables privadas | ||
| + | // de los objetos de tipo Freezer | ||
| + | const _privates = new WeakMap(); | ||
| + | |||
| + | // Para asegurarnos de que el nombre del método que crearemos | ||
| + | // en el proxy es único y no tenga conflictos, utilizaremos un símbolo | ||
| + | const _freeze = Symbol(" | ||
| + | |||
| + | const createConfig = function(pr) { | ||
| + | return { | ||
| + | set (target, key, receiver) { | ||
| + | var prop = target[key]; | ||
| + | if (pr.frozen) { | ||
| + | return prop. | ||
| + | } else { | ||
| + | target[key] = receiver; | ||
| + | return receiver; | ||
| + | } | ||
| + | }, | ||
| + | get (target, key, receiver) { | ||
| + | if (key === _freeze) { | ||
| + | return function(v) { | ||
| + | pr.frozen = v ? true : false; | ||
| + | return pr.frozen; | ||
| + | } | ||
| + | } else { | ||
| + | return target[key]; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class Freezer { | ||
| + | |||
| + | constructor(obj) { | ||
| + | let pr = {}; | ||
| + | | ||
| + | | ||
| + | | ||
| + | } | ||
| + | |||
| + | // Método para " | ||
| + | frozen(value) { | ||
| + | let pr = _privates.get(this); | ||
| + | pr.frozen = value ? true : false; | ||
| + | } | ||
| + | |||
| + | get value() { | ||
| + | let pr = _privates.get(this); | ||
| + | return pr.proxy; | ||
| + | } | ||
| + | | ||
| + | // getter para obtener el símbolo | ||
| + | static get freeze() { | ||
| + | return _freeze; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Probamos: | ||
| + | |||
| + | <code javascript> | ||
| + | var obj = {v:42}; | ||
| + | |||
| + | var freezer = new Freezer(obj); | ||
| + | var proxy = freezer.value; | ||
| + | |||
| + | // Congelamos: | ||
| + | proxy[Freezer.freeze](true); | ||
| + | |||
| + | proxy.v = 100; | ||
| + | |||
| + | proxy,.v; // 42 | ||
| + | |||
| + | obj.v; // 42 | ||
| + | |||
| + | // Descongelamos: | ||
| + | freezer.frozen(false); | ||
| + | |||
| + | proxy.v = 100; | ||
| + | |||
| + | proxy.v; // 100 | ||
| + | obj.v; // 100 | ||
| + | </ | ||
| + | |||
| + | ===== Recursos ===== | ||
| + | |||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
informatica/programacion/cursos/programacion_avanzada_javascript/proxies.1730113265.txt.gz · Última modificación: por tempwin
