Introducci贸n a los proxies de JavaScript en ES6

    Introducci贸n

    En este art铆culo, vamos a hablar sobre los proxies de JavaScript que se introdujeron con la versi贸n de JavaScript. ECMAScript 6 (ES6). Usaremos parte de la sintaxis de ES6 existente, incluido el operador de propagaci贸n en este art铆culo. Por lo tanto, ser谩 煤til si tiene algunos conocimientos b谩sicos sobre ES6.

    驴Qu茅 es un proxy?

    Los proxies de JavaScript tienen la capacidad de cambiar el comportamiento fundamental de objetos y funciones. Podemos extender el lenguaje para que se adapte mejor a nuestros requisitos o simplemente usarlo para cosas como validaci贸n y control de acceso en una propiedad.

    Hasta que se introdujeron los proxies, no ten铆amos acceso de nivel nativo para cambiar el comportamiento fundamental de un objeto, ni una funci贸n. Pero con ellos tenemos la capacidad de actuar como capa intermedia, de cambiar c贸mo se debe acceder al objeto, generar informaci贸n como cu谩ntas veces se ha llamado a una funci贸n, etc.

    Ejemplo de proxy de propiedad

    Comencemos con un ejemplo simple para ver los proxies en acci贸n. Para empezar, creemos un objeto persona con firstName, lastNamey age propiedades:

    const person = {
        firstName: 'John',
        lastName: 'Doe',
        age: 21
    };
    

    Ahora creemos un proxy simple pas谩ndolo al Proxy constructor. Acepta par谩metros llamados target y el handler. Ambos se desarrollar谩n en breve.

    Primero creemos un objeto controlador:

    const handler = {
        get(target, property) {
            console.log(`you have read the property ${property}`);
            return target[property];
        }
    };
    

    As铆 es como puede crear un proxy simple:

    const proxyPerson = new Proxy(person, handler);
    
    console.log(proxyPerson.firstName);
    console.log(proxyPerson.lastName);
    console.log(proxyPerson.age);
    

    Ejecutar este c贸digo deber铆a producir:

    you have read the property firstName
    John
    you have read the property lastName
    Doe
    you have read the property age
    21
    

    Cada vez que acceda a una propiedad de ese objeto proxy, recibir谩 un mensaje de consola con el nombre de la propiedad. Este es un ejemplo muy simple de un proxy JavaScript. Entonces, usando ese ejemplo, familiaric茅monos con algunas terminolog铆as.

    Destino de proxy

    El primer par谩metro, target, es el objeto al que ha adjuntado el proxy. El proxy utilizar谩 este objeto para almacenar datos, lo que significa que si cambia el valor del objeto de destino, el valor del objeto del proxy tambi茅n cambiar谩.

    Si desea evitar esto, puede pasar el objetivo directamente al proxy como un objeto an贸nimo, o puede usar alg煤n m茅todo de encapsulaci贸n para proteger el objeto original creando una Expresi贸n de funci贸n invocada inmediatamente (IIFE), o un singleton .

    Simplemente no exponga su objeto al exterior donde se usar谩 el proxy y todo deber铆a estar bien.

    Un cambio en el objeto de destino original todav铆a se refleja en el proxy:

    console.log(proxyPerson.age);
    person.age = 20;
    console.log(proxyPerson.age);
    
    you have read the property age
    21
    you have read the property age
    20
    

    Controlador de proxy

    El segundo par谩metro del Proxy constructor es el handler, que debe ser un objeto que contenga m茅todos que describan la forma en que desea controlar el targetcomportamiento. Los m茅todos dentro de este controlador, por ejemplo el get() m茅todo, se llaman trampas.

    Al definir un controlador, como el que hemos definido en nuestro ejemplo anterior, podemos escribir l贸gica personalizada para un objeto que de otra manera no lo implementar铆a.

    Por ejemplo, puede crear un proxy que actualice una cach茅 o una base de datos cada vez que se actualice una propiedad del objeto de destino.

    Trampas de proxy

    La trampa get ()

    los get() atrapar incendios cuando alguien intenta acceder a una propiedad espec铆fica. En el ejemplo anterior, usamos esto para imprimir una oraci贸n cuando se accedi贸 a la propiedad.

    Como ya sabr谩, JavaScript no admite propiedades privadas. Entonces, a veces, como una convenci贸n, los desarrolladores usan el gui贸n bajo (_) delante del nombre de la propiedad, por ejemplo, _securityNumber, para identificarlo como propiedad privada.

    Sin embargo, esto en realidad no aplica nada en el nivel de c贸digo. Los desarrolladores simplemente saben que no deben acceder directamente a las propiedades que comienzan con _. Con proxies, podemos cambiar eso.

    Actualicemos nuestro person objeto con un n煤mero de seguro social en una propiedad llamada _ssn:

    const person = {
        firstName: 'John',
        lastName: 'Doe',
        age: 21,
        _ssn: '123-45-6789'
    };
    

    Ahora editemos el get() trap para lanzar una excepci贸n si alguien intenta acceder a una propiedad que comienza con un gui贸n bajo:

    const handler = {
        get(target, property) {
            if (property[0] === '_') {
                throw new Error(`${property} is a private property`);
            }
    
            return target[property];
        }
    }
    
    const proxyPerson = new Proxy(person, handler);
    
    console.log(proxyPerson._ssn);
    

    Si ejecuta este c贸digo, deber铆a ver el siguiente mensaje de error en su consola:

    Error: _ssn is a private property
    

    La trampa del set ()

    Ahora, echemos un vistazo a set() trap, que controla el comportamiento al establecer valores en la propiedad de un objeto de destino. Para darle un ejemplo claro, supongamos que cuando define un person objetar el valor de la age debe estar en el rango de 0 a 150.

    Como ya sabr谩, JavaScript es un lenguaje de escritura din谩mico, lo que significa que una variable puede contener cualquier tipo de valor (cadena, n煤mero, bool, etc.) en cualquier momento. As铆 que normalmente es muy dif铆cil hacer cumplir la age propiedad para contener enteros. Sin embargo, con los proxies, podemos controlar la forma en que establecemos los valores de las propiedades:

    const handler = {
        set(target, property, value) {
            if (property === 'age') {
                if (!(typeof value === 'number')) {
                    throw new Error('Age should be a number');
                }
    
                if (value < 0 || value > 150) {
                    throw new Error("Age value should be in between 0 and 150");
                }
            }
    
            target[property] = value;
        }
    };
    
    const proxyPerson = new Proxy(person, handler);
    proxyPerson.age = 170;
    

    Como puede ver en este c贸digo, el set() trap acepta tres par谩metros, que son:

    • target: El objeto de destino al que se adjunt贸 el proxy
    • property: El nombre de la propiedad que se est谩 configurando
    • value: El valor que se asigna a la propiedad

    En esta trampa, hemos comprobado si el nombre de la propiedad es age, y si es as铆, si tambi茅n es un n煤mero y el valor est谩 entre 0 y 150, arrojar谩 un error si no lo es.

    Cuando ejecuta este c贸digo, deber铆a ver el siguiente mensaje de error en la consola:

    Error: Age value should be in between 0 and 150
    

    Adem谩s, puede intentar asignar un valor de cadena y ver si arroja un error.

    La trampa deleteProperty ()

    Ahora pasemos al deleteProperty() trampa que se activar谩 cuando intente eliminar una propiedad de un objeto:

    const handler = {
        deleteProperty(target, property) {
            console.log('You have deleted', property);
            delete target[property];
        }
    };
    
    const proxyPerson = new Proxy(person, handler);
    
    delete proxyPerson.age;
    

    Como puede ver, el deleteProperty() trampa tambi茅n acepta el target y property par谩metros.

    Si ejecuta este c贸digo, deber铆a ver el siguiente resultado:

    You have deleted age
    

    Usar proxies con funciones

    La trampa de aplicar ()

    los apply() trap se utiliza para identificar cu谩ndo se produce una llamada de funci贸n en el objeto proxy. En primer lugar, creemos una persona con un nombre y un apellido:

    const person = {
        firstName: 'Sherlock',
        lastName: 'Holmes'
    };
    

    Luego, un m茅todo para obtener el nombre completo:

    const getFullName = (person) => {
        return person.firstName + ' ' + person.lastName;
    };
    

    Ahora, creemos un m茅todo proxy que convertir谩 la salida de la funci贸n a letras may煤sculas proporcionando una apply() trampa dentro de nuestro controlador:

    const getFullNameProxy = new Proxy(getFullName, {
        apply(target, thisArg, args) {
            return target(...args).toUpperCase();
        }
    });
    
    console.log(getFullNameProxy(person));
    

    Como puede ver en este ejemplo de c贸digo, el apply() trap ser谩 llamado cuando se llame a la funci贸n. Acepta tres par谩metros: target, thisArg (Cu谩l es el this argumento para la llamada), y el args, que es la lista de argumentos pasados 鈥嬧媋 la funci贸n.

    Hemos utilizado el apply() trap para ejecutar la funci贸n de destino con los argumentos dados utilizando la sintaxis de propagaci贸n de ES6 y convertir el resultado a may煤sculas. Entonces deber铆a ver el nombre completo en may煤sculas:

    SHERLOCK HOLMES
    

    Propiedades calculadas con proxies

    Las propiedades calculadas son las propiedades que se calculan realizando operaciones en otras propiedades existentes. Por ejemplo, digamos que tenemos un person objeto con las propiedades firstName y lastName. Con esto, el nombre completo puede ser una combinaci贸n de esas propiedades, como en nuestro 煤ltimo ejemplo. Por tanto, el nombre completo es una propiedad calculada.

    Primero, creemos de nuevo un person objeto con un nombre y un apellido:

    const person = {
        firstName: 'John',
        lastName: 'Doe'
    };
    

    Entonces podemos crear un controlador con el get() trap para devolver el nombre completo calculado, que se logra creando un proxy del person:

    const handler = {
        get(target, property) {
            if (property === 'fullName') {
                return target.firstName + ' ' + target.lastName;
            }
    
            return target[property];
        }
    };
    
    const proxyPerson = new Proxy(person, handler);
    

    Ahora intentemos acceder al nombre completo de la persona proxy:

    console.log(proxyPerson.fullName);
    
    John Doe
    

    Utilizando solo el proxy, hemos creado un m茅todo “getter” en el person objeto sin tener que cambiar realmente el objeto original.

    Ahora, veamos otro ejemplo que es m谩s din谩mico de lo que hemos encontrado hasta ahora. Esta vez, en lugar de devolver solo una propiedad, devolveremos una funci贸n que se crea din谩micamente en funci贸n del nombre de funci贸n dado.

    Considere una variedad de personas, donde cada objeto tiene un id de la persona, nombre de la persona y la edad de la persona. Necesitamos consultar a una persona por el id, nameo age. As铆 que simplemente podemos crear algunos m茅todos, getById, getByNamey getByAge. Pero esta vez vamos a llevar las cosas un poco m谩s lejos.

    Queremos crear un controlador que pueda hacer esto para una matriz que puede tener cualquier propiedad. Por ejemplo, si tenemos una serie de libros y cada libro tiene una propiedad isbn, tambi茅n deber铆amos poder consultar esta matriz usando getByIsbn y el m茅todo debe generarse din谩micamente en el tiempo de ejecuci贸n.

    Pero por el momento creemos una variedad de personas.

    const people = [
        {
            id: 1,
            name: 'John Doe',
            age: 21
        },
        {
            id: 2,
            name: 'Ann Clair',
            age: 24
        },
        {
            id: 3,
            name: 'Sherlock Holmes',
            age: 35
        }
    ];
    

    Ahora creemos un get trap para generar la funci贸n din谩mica seg煤n el nombre de la funci贸n.

    const proxyPeople = new Proxy(people, {
        get(target, property) {
            if (property.startsWith('getBy')) {
                let prop = property.replace('getBy', '')
                                   .toLowerCase();
    
                return function(value) {
                    for (let i of target) {
                        if (i[prop] === value) {
                            return i;
                        }
                    }
                }
            }
    
            return target[property];
        }
    });
    

    En este c贸digo, primero verificamos si el nombre de la propiedad comienza con “getBy”, luego eliminamos “getBy” del nombre de la propiedad, por lo que terminamos con el nombre real de la propiedad que queremos usar para consultar el elemento. Entonces, por ejemplo, si el nombre de la propiedad es getById, terminamos con id como propiedad de la consulta.

    Ahora tenemos el nombre de propiedad con el que queremos consultar, por lo que podemos devolver una funci贸n que acepta un valor e iterar a trav茅s de la matriz para encontrar un objeto con ese valor y en la propiedad dada.

    Puede probar esto ejecutando lo siguiente:

    console.log(proxyPeople.getById(1));
    console.log(proxyPeople.getByName('Ann Clair'));
    console.log(proxyPeople.getByAge(35));
    

    El objeto de persona relevante para cada llamada debe mostrarse en la consola:

    { id: 1, name: 'John Doe', age: 21 }
    { id: 2, name: 'Ann Clair', age: 24 }
    { id: 3, name: 'Sherlock Holmes', age: 35 }
    

    En la primera l铆nea usamos proxyPeople.getById(1), que luego devolvi贸 al usuario con un id de 1. En la segunda l铆nea usamos proxyPeople.getByName('Ann Clair'), que devolvi贸 a la persona con el nombre “Ann Clair”, y as铆 sucesivamente.

    Como ejercicio para el lector, intente crear su propia matriz de libros con propiedades isbn, titley author. Luego, usando un c贸digo similar al anterior, vea c贸mo puede usar getByIsbn, getByTitley getByAuthor para recuperar elementos de la lista.

    Por simplicidad, en esta implementaci贸n hemos asumido que solo hay un objeto con un cierto valor para cada propiedad. Pero este podr铆a no ser el caso en algunas situaciones, que luego puede editar ese m茅todo para devolver una matriz de objetos que coinciden con la consulta dada.

    Conclusi贸n

    El c贸digo fuente de este art铆culo est谩 disponible en GitHub como siempre. Use esto para comparar su c贸digo si se atasc贸 en el tutorial.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *