Como Usar Metaprogramação em JavaScript: Object.defineProperty e Decorators em Produção Já leu

Object.defineProperty: Controle Fino sobre Propriedades Object.defineProperty é o alicerce da metaprogramação em JavaScript. Ele permite definir propriedades de objetos com controle granular sobre comportamentos como leitura, escrita e enumeração. Diferentemente da atribuição simples, este método possibilita criar getters, setters e descriptores que interceptam operações. ${chave} ${chave} Campo ${chave} alterado para ${valor} ${chave} Este padrão é fundamental em frameworks modernos para reatividade automática e serialização inteligente de dados. Conclusão Metaprogramação em JavaScript, através de Object.defineProperty e Decorators, permite criar código mais elegante, seguro e manutenível. Primeiro, Object.defineProperty fornece controle preciso sobre propriedades, essencial para validação e proteção de dados. Segundo, Decorators oferecem uma sintaxe limpa e reutilizável para separar concerns de infraestrutura da lógica de negócio. Terceiro, combinados com Reflect, esses mecanismos possibilitam frameworks reativos e sistemas de auditoria sofisticados—compreendê-los é fundamental para dominar JavaScript moderno. Referências MDN - Object.defineProperty MDN - Decorators Proposal JavaScript.info - Property flags and descriptors TC39 - Reflect API Specification You Don't Know JS - Types

Object.defineProperty: Controle Fino sobre Propriedades

Object.defineProperty é o alicerce da metaprogramação em JavaScript. Ele permite definir propriedades de objetos com controle granular sobre comportamentos como leitura, escrita e enumeração. Diferentemente da atribuição simples, este método possibilita criar getters, setters e descriptores que interceptam operações.

const usuario = {};

Object.defineProperty(usuario, 'nome', {
  value: 'João',
  writable: false,    // impede alteração
  enumerable: true,   // aparece em for...in
  configurable: false // impede redefinição
});

usuario.nome; // 'João'
usuario.nome = 'Maria'; // silencioso em modo não-strict
console.log(usuario.nome); // 'João'

// Com getter e setter
Object.defineProperty(usuario, 'idade', {
  get() {
    return this._idade || 0;
  },
  set(value) {
    if (value < 0) throw new Error('Idade inválida');
    this._idade = value;
  },
  enumerable: true,
  configurable: true
});

usuario.idade = 25; // ativa o setter
console.log(usuario.idade); // 25

Esse controle é fundamental para validação, reatividade e proteção de dados. Frameworks como Vue.js usam getters/setters para detectar mudanças e atualizar a interface automaticamente.

Validação e Proteção de Dados com Descriptores

Combinando Object.defineProperty com lógica de negócio, criamos camadas de proteção robustas. Um caso prático é forçar validação antes de armazenar valores ou permitir apenas leitura de certas propriedades.

class Produto {
  constructor(nome, preco) {
    this._preco = preco;

    Object.defineProperty(this, 'preco', {
      get() {
        return this._preco;
      },
      set(valor) {
        if (typeof valor !== 'number' || valor < 0) {
          throw new TypeError('Preço deve ser um número positivo');
        }
        console.log(`Preço atualizado: R$ ${valor}`);
        this._preco = valor;
      },
      enumerable: true
    });

    Object.defineProperty(this, 'nome', {
      value: nome,
      writable: false,
      enumerable: true
    });
  }
}

const produto = new Produto('Notebook', 2500);
produto.preco = 2800; // Preço atualizado: R$ 2800
produto.preco = -100; // TypeError: Preço deve ser um número positivo
produto.nome = 'Desktop'; // silencioso, não altera

Essa abordagem é essencial em aplicações financeiras, sistemas de permissões e validação de dados em tempo real.

Decorators: Sintaxe Moderna para Metaprogramação

Decorators são uma sintaxe elegante (ainda experimental em JavaScript, mas já padrão em TypeScript) que encapsula a lógica de Object.defineProperty e reflection. Um decorator é uma função que modifica o comportamento de classes, métodos ou propriedades.

// Decorator simples para logging
function log(target, propertyKey, descriptor) {
  const metodoOriginal = descriptor.value;

  descriptor.value = function(...args) {
    console.log(`Chamando ${propertyKey} com args:`, args);
    const resultado = metodoOriginal.apply(this, args);
    console.log(`${propertyKey} retornou:`, resultado);
    return resultado;
  };

  return descriptor;
}

class Calculadora {
  @log
  somar(a, b) {
    return a + b;
  }
}

const calc = new Calculadora();
calc.somar(5, 3);
// Chamando somar com args: [5, 3]
// somar retornou: 8

Para usar decorators no JavaScript padrão (sem TypeScript), você precisará de um transpilador. Mas a ideia é poderosa: separar a lógica de negócio da infraestrutura.

Decorators Reutilizáveis

// Decorator para cachear resultados
function cache(target, propertyKey, descriptor) {
  const metodo = descriptor.value;
  const cache = new Map();

  descriptor.value = function(...args) {
    const chave = JSON.stringify(args);
    if (cache.has(chave)) {
      console.log('Retornando do cache');
      return cache.get(chave);
    }
    const resultado = metodo.apply(this, args);
    cache.set(chave, resultado);
    return resultado;
  };

  return descriptor;
}

class Fibonacci {
  @cache
  calcular(n) {
    console.log(`Calculando fib(${n})`);
    if (n <= 1) return n;
    return this.calcular(n - 1) + this.calcular(n - 2);
  }
}

const fib = new Fibonacci();
fib.calcular(5);
fib.calcular(5); // Retornando do cache

Decorators são particularmente úteis em autenticação, validação, auditoria e transformação de dados de forma declarativa.

Reflection e Introspeção Avançada

A verdadeira potência da metaprogramação emerge quando combinamos Object.defineProperty com Reflect API para introspecção. Isso permite inspecionar e modificar estruturas em tempo de execução com segurança.

class Usuario {
  constructor(dados) {
    // Cria propriedades reativas
    Object.keys(dados).forEach(chave => {
      Object.defineProperty(this, chave, {
        get() {
          return this[`_${chave}`];
        },
        set(valor) {
          if (Reflect.has(this, `_${chave}`)) {
            console.log(`Campo ${chave} alterado para ${valor}`);
          }
          this[`_${chave}`] = valor;
        },
        enumerable: true
      });
    });

    // Popula valores iniciais
    Object.assign(this, dados);
  }

  toJSON() {
    return Object.getOwnPropertyNames(this)
      .filter(p => !p.startsWith('_'))
      .reduce((obj, chave) => {
        obj[chave] = this[chave];
        return obj;
      }, {});
  }
}

const user = new Usuario({ email: 'teste@mail.com', ativo: true });
user.email = 'novo@mail.com'; // Campo email alterado para novo@mail.com
console.log(JSON.stringify(user)); // {"email":"novo@mail.com","ativo":true}

Este padrão é fundamental em frameworks modernos para reatividade automática e serialização inteligente de dados.

Conclusão

Metaprogramação em JavaScript, através de Object.defineProperty e Decorators, permite criar código mais elegante, seguro e manutenível. Primeiro, Object.defineProperty fornece controle preciso sobre propriedades, essencial para validação e proteção de dados. Segundo, Decorators oferecem uma sintaxe limpa e reutilizável para separar concerns de infraestrutura da lógica de negócio. Terceiro, combinados com Reflect, esses mecanismos possibilitam frameworks reativos e sistemas de auditoria sofisticados—compreendê-los é fundamental para dominar JavaScript moderno.

Referências


Artigos relacionados