Prólogo
Quero apresentar ao seu tribunal várias mini estátuas que descreverão as técnicas e os fundamentos da metaprogramação. Escreverei principalmente sobre o uso de certas técnicas em JavaScript ou em TypeScript
Este é o primeiro (e espero que não seja o último) artigo da série.
Então, o que é metaprogramação
A metaprogramação é uma técnica de programação na qual os programas de computador podem tratar outros programas como dados. Isso significa que um programa pode ser projetado para ler, gerar, analisar ou transformar outros programas e até mesmo se modificar durante a execução. Em alguns casos, isso permite que os programadores minimizem o número de linhas de código para expressar uma solução, reduzindo o tempo de desenvolvimento .
Uma descrição bastante confusa, mas o principal benefício da metaprogramação é compreensível:
... isso permite que os programadores minimizem o número de linhas de código para implementar a solução, o que reduz o tempo de desenvolvimento

De fato, a metaprogramação tem muita aparência e disfarce. E você pode discutir por um longo tempo sobre "onde a metaprogramação termina e a própria programação começa"
Por mim, aceitei as seguintes regras:
- A metaprogramação não lida com a lógica de negócios, não a altera e não a afeta de forma alguma.
- Se você remover todo o código de metaprogramação, isso não deve afetar radicalmente o programa.
Em JavaScript, a metaprogramação é uma tendência relativamente nova, cujo bloco base é o descritor.
Descritor JavaScript
Descritor é um tipo de descrição (meta-informação) de uma determinada propriedade ou método em um objeto.
Compreender e manipular adequadamente esse objeto ( descritor ) permite muito mais do que apenas criar e alterar métodos ou propriedades em objetos.
O descritor também ajudará a entender o trabalho com os decoradores (mas mais sobre isso no próximo artigo).
Para maior clareza, imagine que nosso objeto seja uma descrição do apartamento.
Nós descrevemos o objeto do nosso apartamento:
let apt = { floor: 12, number: '12B', size: 3400, bedRooms: 3.4, bathRooms: 2, price: 400000, amenities: {...} };
Vamos determinar quais das propriedades são modificáveis e quais não são.
Por exemplo, o piso ou o tamanho total do apartamento não podem ser alterados, mas o número de quartos ou banheiros é bem possível.
E, portanto, temos o seguinte requisito: nos objetos apt , torna impossível alterar as propriedades: piso e tamanho .
Para resolver esse problema, precisamos apenas de descritores de cada uma dessas propriedades. Para obter o descritor , usamos o método estático getOwnPropertyDescriptor , que pertence à classe Object .
let descriptor = Object.getOwnPropertyDescriptor(todoObject, 'floor'); console.log(descriptor);
Vamos analisar em ordem:
value: any - na verdade o mesmo valor que em algum momento foi atribuído à propriedade floor
writable: boolean - determina se deve ou não alterar o valor
enumerable: boolean - determina se a propriedade do piso pode ou não ser listada - (mais sobre isso mais tarde).
configurable: boolean - Define a capacidade de fazer alterações no objeto descritor .
Para evitar a possibilidade de alterar a propriedade do piso , após a inicialização, é necessário alterar o valor de gravável para falso .
Para alterar as propriedades de um descritor, existe um método estático defineProperty , que pega o objeto em si, o nome da propriedade e o descritor .
Object.defineProperty(apt, 'floor', {writable: false});
Neste exemplo, não passamos o objeto descritor inteiro, mas apenas uma propriedade gravável com o valor false .
Agora vamos tentar alterar o valor na propriedade floor:
apt.floor = 44; console.log(apt.floor);
O valor não mudou e, ao usar 'use strict', recebemos uma mensagem de erro:
Não é possível atribuir para ler apenas a propriedade 'floor' do objeto ...
E agora não podemos mais alterar o valor. No entanto, ainda podemos retornar gravável -> true e alterar a propriedade floor . Para evitar isso, é necessário alterar o valor da propriedade configurável para false no descritor .
Object.defineProperty(apt, 'floor', {writable: false, configurable: false});
Se agora tentarmos alterar o valor de qualquer uma das propriedades do nosso descritor ...
Object.defineProperty(apt, 'floor', {writable: true, configurable: true});
Em resposta, obtemos:
TypeError: Não é possível redefinir a propriedade: floor
Em outras palavras, não podemos mais alterar o valor de floor nem seu descritor .
Resumir
Para manter o valor da propriedade no objeto inalterado, é necessário registrar a configuração dessa propriedade: {gravável: false, configurável: false} .
Isso pode ser feito como durante a inicialização da propriedade:
Object.defineProperty(apt, 'floor', {value: 12, writable: false, configurable: false});
Ou depois.
Object.defineProperty(apt, 'floor', {writable: false, configurable: false});
No final, considere um exemplo com uma classe:
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: {...} };
Mude o método getFloor:
Apartment.prototype.getFloor = () => { return 44 }; let myApt = new Apartment(apt); console.log(myApt);
Agora mude o descritor do 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 artigo lance um pouco mais de luz sobre o que é um descritor e como ele pode ser usado.
Tudo o que foi escrito acima não afirma ser absolutamente verdadeiro ou o único correto.