Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_avanzada_javascript:proxies

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
informatica:programacion:cursos:programacion_avanzada_javascript:proxies [2024/10/28 13:26] – [DEMO: Interceptando un getter a propiedad] tempwininformatica:programacion:cursos:programacion_avanzada_javascript:proxies [2024/10/30 16:03] (actual) – [Recursos] tempwin
Línea 214: Línea 214:
 foo(proxy); // 42 foo(proxy); // 42
 </code> </code>
 +
 +Si modificamos la función ''foo'' tal que así:
 +
 +<code javascript>
 +function foo(p) {
 +    console.log(p.prop)
 +    console.log(p.cualquierCosa);
 +}
 +</code>
 +
 +Y ejecutamos:
 +
 +<code javascript>
 +foo(proxy); // 42
 +            // NaN
 +</code>
 +
 +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
 +};
 +</code>
 +
 +Se quiere que la propiedad ''_pr'' no sea accesible haciendo ''obj._pr''. Intentar acceder a la propiedad para leerla debe devolver ''undefined'' y acceder para modificar su valor debe ser ignorado.
 +
 +Tienes la solución en el siguiente apartado y en el fichero ''privateHidder.js'' pero inténtalo antes por tu cuenta.
 +
 +==== 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;
 +        }
 +    }
 +}
 +</code>
 +
 +Probamos:
 +
 +<code javascript>
 +obj.v = 1200;
 +obj._v = -10;
 +
 +console.log(obj._v); // -10
 +</code>
 +
 +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, 1) === "_") {
 +                return undefined;
 +            } else {
 +                return target[key];
 +            }
 +        },
 +        set (target, key, receiver) {
 +            if (key.substr(0, 1) !== "_") {
 +                target[key] = receiver;
 +            }
 +        }
 +    }
 +    
 +    return new Proxy(obj, handler);
 +}
 +</code>
 +
 +Vamos a usarlo:
 +
 +<code javascript>
 +let test = hidePrivates(obj);
 +
 +console.log(test._v); // undefined
 +test.v = 1200;
 +test._v = 100;
 +
 +console.log(obj._v); // 1200
 +</code>
 ===== Proxies a objetos de tipo función ===== ===== Proxies a objetos de tipo función =====
  
 +Ya hemos visto cómo con el método ''get'' podemos interceptar una llamada a una función del objeto envuelto por el proxy. Pero además existe otra posible intercepción y es cuando creamos un **proxy a una función**.
 +
 +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:
 +
 +  * ''target'': Como en los otros casos contiene el objeto envuelto por el proxy
 +  * ''thisArg'': El valor de ''this'' usado
 +  * ''argumentsList'': Un array con los argumentos. Si no se pasan argumentos es un array vacío (no vale nunca ''undefined'').
 +
 +Veamos un ejemplo:
 +
 +<code javascript>
 +let foo = function(...values) {
 +    let result = 0;
 +    values.forEach(s => result += s);       
 +    console.log('Sum of values is ' + result);
 +}
 +
 +let proxy = new Proxy(foo, {
 +    apply(target, thisArg, argumentsList) {
 +        let args = argumentsList.map(arg => typeof(arg) === "number" ? arg : 0);
 +        return target.apply(thisArg, args);
 +    }
 +});
 +
 +proxy(10,20,"30");
 +</code>
 +
 +El proxy intercepta todas las llamadas a ''foo'' y sustituye por 0 todos los parámetros no numéricos. Es por ello que la llamada ''proxy(10, 20, "30")'' devuelve 30 (mientras que ''foo(10, 20, "30")'' devuelve "3030".
 +
 +==== Interceptando call, apply y bind ====
 +
 +El uso de ''call'' y ''apply'' se intercepta también de forma automática. Dado el mismo código anterior una llamada a ''proxy.call'' o ''proxy.apply'' es interceptada correctamente (y en este caso el parámetro ''thisArg'' toma el valor correspondiente):
 +
 +<code javascript>
 +proxy.apply([10], [10, 20, "30"]);
 +proxy.call([20], 10, 20, "30");
 +</code>
 +
 +En ambos casos se intercepta la llamada (se ejecuta el método ''apply'' del //handler//). En el primer caso el valor de ''thisArg'' es ''[10]'' y en el segundo es ''[20]''. En ambos casos ''argumentsList'' es ''[10, 20, "30"]'' (un array).
 +
 +Del mismo modo dado un proxy a una función, si usamos ''bind'' para obtener otra función atada permanentemente a otro valor de ''this'', las llamadas a esa otra función también son capturadas:
 +
 +<code javascript>
 +let proxy2 = proxy.bind("otherhis");
 +proxy(10, 20, "30");        // Se captura la llamada, thisArg vale "otherthis" (y devuelve 30 y no "3030").
 +</code>
 +
 +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, auditorías de llamadas a funciones, enrutamiento dinámico de funciones... son escenarios que con ES5 ya se podían realizar, pero que requerían mucho más código y no eran, ni de lejos, tan transparentes ni sencillos como con los proxies.
 +
 +Vamos a ver a continuación un caso práctico de uso.
 ===== DEMO: Creando un "congelador" de objetos - Parte 1 ===== ===== DEMO: Creando un "congelador" de objetos - Parte 1 =====
  
 +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 = {};
 +       pr.frozen = false; // el objeto está congelado?
 +       _privates.set(this, pr)
 +       pr.proxy = new Proxy(obj, {});
 +    }
 +    
 +    // Método para "congelar" o "descongelar" el objeto
 +    frozen(value) {
 +        let pr = _privates.get(this);
 +        pr.frozen = value ? true : false;
 +    }
 +    
 +    get value() {
 +        let pr = _privates.get(this);
 +        return pr.proxy;
 +    }
 +}
 +</code>
 +
 +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 }
 +</code>
 +
 +Ahora tendremos que añadir la funcionalidad para congelar el objeto.
 ===== DEMO: Creando un "congelador" de objetos - Parte 2 ===== ===== DEMO: Creando un "congelador" de objetos - Parte 2 =====
  
 +<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 = {};
 +       pr.frozen = false; // el objeto está congelado?
 +       _privates.set(this, pr)
 +       pr.proxy = new Proxy(obj, {});
 +    }
 + 
 +    // Método para "congelar" o "descongelar" el objeto
 +    frozen(value) {
 +        let pr = _privates.get(this);
 +        pr.frozen = value ? true : false;
 +    }
 + 
 +    get value() {
 +        let pr = _privates.get(this);
 +        return pr.proxy;
 +    }
 +}
 +</code>
 +
 +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
 +</code>
 ===== DEMO: Creando un "congelador" de objetos - Parte 3 ===== ===== DEMO: Creando un "congelador" de objetos - Parte 3 =====
  
 +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 ''proxy[Freezer.freeze](true);''.
 +
 +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("freeze");
 + 
 +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 = {};
 +       pr.frozen = false; // el objeto está congelado?
 +       _privates.set(this, pr)
 +       pr.proxy = new Proxy(obj, {});
 +    }
 + 
 +    // Método para "congelar" o "descongelar" el objeto
 +    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;
 +    } 
 +}
 +</code>
 +
 +Probamos:
 +
 +<code javascript>
 +var obj = {v:42};
 + 
 +var freezer = new Freezer(obj);
 +var proxy = freezer.value;
 +
 +// Congelamos:
 +proxy[Freezer.freeze](true); // true
 +
 +proxy.v = 100;
 +
 +proxy,.v; // 42
 +
 +obj.v; // 42
 +
 +// Descongelamos:
 +freezer.frozen(false);
 +
 +proxy.v = 100;
 +
 +proxy.v; // 100
 +obj.v; // 100
 +</code>
 +
 +===== Recursos =====
 +
 +  * [[https://developer.chrome.com/blog/es2015-proxies| Introducing ES2015 proxies]]
 +  * [[https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Extending_constructor|Extendiendo un constructor con un Proxy]] (MDN)
 +  * [[https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Proxy#manipulando_nodos_del_dom|Manipulando nodos del DOM]] (MDN)
informatica/programacion/cursos/programacion_avanzada_javascript/proxies.1730118403.txt.gz · Última modificación: por tempwin