Prologo
Quiero presentar a su corte una serie de mini estatuas que describirán las técnicas y los fundamentos de la metaprogramación. Escribiré principalmente sobre el uso de ciertas técnicas en JavaScript o en TypeScript
Este es el primer (y con suerte no el último) artículo de la serie.
Entonces, ¿qué es la metaprogramación?
La metaprogramación es una técnica de programación en la cual los programas de computadora tienen la capacidad de tratar a otros programas como sus datos. Significa que un programa puede diseñarse para leer, generar, analizar o transformar otros programas e incluso modificarse a sí mismo mientras se ejecuta. En algunos casos, esto permite a los programadores minimizar la cantidad de líneas de código para expresar una solución, lo que a su vez reduce el tiempo de desarrollo .
Una descripción bastante confusa, pero el principal beneficio de la metaprogramación es comprensible:
... esto permite a los programadores minimizar la cantidad de líneas de código para implementar la solución, lo que a su vez reduce el tiempo de desarrollo

De hecho, la metaprogramación tiene mucha cara y apariencia. Y puede discutir durante mucho tiempo sobre "dónde termina la metaprogramación y comienza la programación en sí"
Para mí, acepté las siguientes reglas:
- La metaprogramación no trata con la lógica de negocios, no la cambia y no la afecta de ninguna manera.
- Si elimina todo el código de metaprogramación, esto no debería afectar (radicalmente) al programa.
En JavaScript, la metaprogramación es una tendencia relativamente nueva, cuyo ladrillo base es el descriptor.
Descriptor de JavaScript
El descriptor es un tipo de descripción (metainformación) de una determinada propiedad o método en un objeto.
Comprender y manipular adecuadamente este objeto ( descriptor ) permite mucho más que simplemente crear y cambiar métodos o propiedades en los objetos.
También el descriptor ayudará a comprender el trabajo con los decoradores (pero más sobre eso en el próximo artículo).
Para mayor claridad, imagine que nuestro objeto es una descripción del apartamento.
Describimos el objeto de nuestro apartamento:
let apt = { floor: 12, number: '12B', size: 3400, bedRooms: 3.4, bathRooms: 2, price: 400000, amenities: {...} };
Determinemos cuáles de las propiedades son modificables y cuáles no.
Por ejemplo, el piso o el tamaño total del apartamento no se pueden cambiar, pero el número de habitaciones o baños es bastante posible.
Por lo tanto, tenemos el siguiente requisito: en los objetos aptos , es imposible cambiar las propiedades: piso y tamaño .
Para resolver este problema, solo necesitamos descriptores de cada una de estas propiedades. Para obtener el descriptor , utilizamos el método estático getOwnPropertyDescriptor , que pertenece a la clase Object .
let descriptor = Object.getOwnPropertyDescriptor(todoObject, 'floor'); console.log(descriptor);
Analicemos en orden:
valor: cualquiera - en realidad el mismo valor que en algún momento se asignó a la propiedad del piso
escribible: booleano : determina si se cambia o no el valor
enumerable: boolean - determina si la propiedad del piso puede o no ser listada - (más sobre eso más adelante).
configurable: boolean : define la capacidad de realizar cambios en el objeto descriptor .
Para evitar la posibilidad de cambiar la propiedad del piso , después de la inicialización, es necesario cambiar el valor de escritura a falso .
Para cambiar las propiedades de un descriptor, hay un método estático defineProperty , que toma el objeto en sí, el nombre de la propiedad y el descriptor .
Object.defineProperty(apt, 'floor', {writable: false});
En este ejemplo, no pasamos todo el objeto descriptor , sino solo una propiedad de escritura con el valor false .
Ahora intentemos cambiar el valor en la propiedad del piso:
apt.floor = 44; console.log(apt.floor);
El valor no ha cambiado, y cuando usamos 'use estricto' recibimos un mensaje de error:
No se puede asignar a la propiedad de solo lectura 'piso' del objeto ...
Y ahora ya no podemos cambiar el valor. Sin embargo, todavía podemos devolver escribible -> verdadero y luego cambiar la propiedad del piso . Para evitar esto, es necesario cambiar el valor de la propiedad configurable a falso en el descriptor .
Object.defineProperty(apt, 'floor', {writable: false, configurable: false});
Si ahora intentamos cambiar el valor de cualquiera de las propiedades de nuestro descriptor ...
Object.defineProperty(apt, 'floor', {writable: true, configurable: true});
En respuesta, obtenemos:
TypeError: No se puede redefinir la propiedad: piso
En otras palabras, ya no podemos cambiar el valor de floor ni su descriptor .
Resumir
Para que el valor de la propiedad en el objeto no cambie, es necesario registrar la configuración de esta propiedad: {escribible: falso, configurable: falso} .
Esto se puede hacer como durante la inicialización de la propiedad:
Object.defineProperty(apt, 'floor', {value: 12, writable: false, configurable: false});
O despues.
Object.defineProperty(apt, 'floor', {writable: false, configurable: false});
Al final, considere un ejemplo con una clase:
class Apartment { constructor(apt) { this.apt = apt; } getFloor() { return this.apt.floor } } let apt = { floor: 12, number: '12B', size: 3400, bedRooms: 3.4, bathRooms: 2, price: 400000, amenities: {...} };
Cambia el método getFloor:
Apartment.prototype.getFloor = () => { return 44 }; let myApt = new Apartment(apt); console.log(myApt);
Ahora cambie el descriptor del método getFloor () :
Object.defineProperty(Apartment.prototype, 'getFloor', {writable: false, configurable: false}); Apartment.prototype.getFloor = () => { return 44 }; let myApt = new Apartment(apt); console.log(myApt);
Espero que este artículo arroje un poco más de luz sobre qué es el descriptor y cómo se puede usar.
Todo lo escrito anteriormente no pretende ser absolutamente cierto o el único correcto.