DEV Community

Erandir Junior
Erandir Junior

Posted on • Updated on

#JS - Manipulando objetos com Proxy

Apresentação

Olá devs, antes de adentrarmos no conteúdo do artigo, gostaria de me apresentar: para quem não me conhece, me chamo Erandir Junior, sou desenvolvedor web, atuo tanto no front como back, apesar de ser mais backend. Essa plataforma será nosso ponto de enconto, realmente espero que gostem.

Objetos

Sabemos que é muito simples definir um objeto em JavaScript, podemos criar um objeto utilizando a forma literal, função construtora ou mesmo por meio da Object API. Outro ponto, é que é muito fácil manipular propriedades, ou seja, acessar, modificar e adicionar.

E para deixar isso de forma clara, vamos criar um pequeno exemplo, onde definiremos um objeto com algumas propriedades, e depois, vamos manipular esse objeto:

const user = {
  name: 'Erandir Junior',
  email: 'erandir@email.com',
  password: '123456',
  uuid: '1234-1234-1234-1234'
};

// Exibindo valor da propriedade name
console.log(user.name); // Erandir Junior

// Atribuindo um novo valor para a propriedade uuid
user.uuid = '2222-2222-2222-2222';

// Adicionando uma nova propriedade
user.createdAt = '2021-01-01';
Enter fullscreen mode Exit fullscreen mode

Problema

Como temos algo bastante dinâmico, podemos modificar propriedades que não deveriam ser modificadas, como por exemplo a propriedade uuid. Nós não queremos modificar esse valor, o que poderíamos ter feito para que isso não ocorresse? Bem, poderíamos aplicar algumas convenções:

const user = {
  _name: 'Erandir Junior',
  _email: 'erandir@email.com',
  _password: '123456',
  _uuid: '1234-1234-1234-1234',
  get name() {
    return this._name;
  },
  set name(name) {
    this._name = name;
  },
  get email() {
    return this._email;
  },
  set email(email) {
    this._email = email;
  },
  get password() {
    return this._password;
  },
  set password(password) {
    this._password = password;
  },
  get uuid() {
    return this._uuid;
  }
};

// Exibindo valor da propriedade name
console.log(user.name); // Erandir Junior

// Atribuindo um novo valor para a propriedade name
user.name = 'Erandir';
Enter fullscreen mode Exit fullscreen mode

Adicionamos os dos métodos get e set, para acessar e modificar os valores do nosso objeto, além disso, adicionamos um _ antes dos nomes das propriedades, para informar que aquelas propriedades são "privadas". Falando um pouco mais do método set, podemos injetar alguma lógica. Um exemplo que posso pensar, seria o de verificar se um email é válido ou se um nome passado tem pelo menos uma quantidade aceitável de caracteres:

const user = {
  _name: 'Erandir Junior',
  _email: 'erandir@email.com',
  _password: '123456',
  _uuid: '1234-1234-1234-1234',
  get name() {
    return this._name;
  },
  set name(name) {
    if(name.length < 3) {
        throw Error('Minimum 3 characters!');
    }

    this._name = name;
  },
  get email() {
   return this._email;
  },
  set email(email) {
    this._email = email;
  },
  get password() {
    return this._password;
  },
  set password(password) {
    this._password = password;
  },
  get uuid() {
    return this._uuid;
  }
};
Enter fullscreen mode Exit fullscreen mode

Problema resolvido?

Não. Poderíamos criar um método set para a propriedade _uuid, onde uma exceção fosse levantada caso alguém tentasse modificar o valor, mas isso não resolveria nosso problema. Nós simplesmente aplicamos uma convenção, ainda podemos acessar diretamente uma propriedade ou mesmo adicionar novas propriedades, como no exemplo abaixo:

// Modificando diretamente o valor da propriedade
user._uuid = 1;

// Adicionando uma nova propriedade
user.createdAt = '2021-01-01';
Enter fullscreen mode Exit fullscreen mode

Então, qual a solução?

Proxy

Sim, temos uma solução. Desde o lançamento do ES6, contamos com o objeto Proxy. Com ele, podemos sobrescrever algumas ações que são padrões em objetos. O objeto Proxy é bem simples de entender, ele recebe 2 parâmetros: o objeto a ser manipulado e um objeto contendo as "armadilhas" (traps), que eu particularmente gosto de chamar de configurações:

const target = {};
const settings = {};
const proxy = new Proxy(target, settings);
Enter fullscreen mode Exit fullscreen mode

Resolvendo o problema

Agora que conhecemos o objeto Proxy, iremos criar uma configuração para checar qual propriedade está sendo alterada o valor, e caso seja a propriedade _uuid, lançamos uma exceção, vejam:

const user = {
  _name: 'Erandir Junior',
  _email: 'erandir@email.com',
  _password: '123456',
  _uuid: '1234-1234-1234-1234',
  get name() {
    return this._name;
  },
  set name(name) {
    if(name.length < 3) {
        throw Error('Minimum 3 characters!');
    }

    this._name = name;
  },
  get email() {
    return this._email;
  },
  set email(email) {
    this._email = email;
  },
  get password() {
    return this._password;
  },
  set password(password) {
     this._password = password;
  },
  get uuid() {
    return this._uuid;
  }
};

const userProxy = new Proxy(user, {
  set (target, key, value) {
      if (key === '_uuid') {
          throw Error('This property cannot be modified!');
      }

      target[key] = value;
   }
});
Enter fullscreen mode Exit fullscreen mode

O método set do segundo objeto passado para Proxy, recebe 3 parâmetros: o objeto alvo, a chave desse objeto e o valor. Com isso, podemos fazer inúmeras checagens e verificações, podemos até mesmo bloquear qualquer modificação no nosso objeto. No exemplo acima, só bloqueamos a modificação do valor de _uuid, caso tentassemos modificar o valor dessa propriedade, receberíamos uma mensagem de erro, o restante do fluxo continua funcionando normalmente:

userProxy._uuid = []; // Uncaught Error: This property cannot be modified!
Enter fullscreen mode Exit fullscreen mode

Mais configurações

Podemos definir inúmeras configurações, vamos definir uma configuração para bloquear o acesso direto as propriedades do nosso objeto, além disso, também iremos bloquear o acesso a propriedades não definidas e não permitiremos a atribuição de novas propriedades:

const user = {
  _name: 'Erandir Junior',
  _email: 'erandir@email.com',
  _password: '123456',
  _uuid: '1234-1234-1234-1234',
  get name() {
    return this._name;
  },
  set name(name) {
    if(name.length < 3) {
        throw Error('Minimum 3 caracters!');
    }

    this._name = name;
  },
  get email() {
    return this._email;
  },
  set email(email) {
    this._email = email;
  },
  get password() {
    return this._password;
  },
  set password(password) {
    this._password = password;
  },
  get uuid() {
    return this._uuid;
  }
};


const userProxy = new Proxy(user, {
  set (target, key, value) {
    if (key === '_uuid') {
        throw Error('This property cannot be modified!');
    }

    if (!(key in target)) {
        throw Error('Property not found!');     
    }

    target[key] = value;
  },
  get (target, key) {
    if (key.startsWith('_')) {
        throw Error('Property cannot be access!');
    }

    if (!(key in target)) {
        throw Error('Property not found!');     
    }

    return target[key];
  }
});
Enter fullscreen mode Exit fullscreen mode

Então pessoal, caso queiram acessar uma determinada propriedade diretamente, obterão um erro, caso tentem acessar uma propriedade não definida, obterão um erro, caso tentem adicionar uma nova propriedade, isso mesmo, obterão um erro.

Resumo

Com o objeto Proxy, podemos sobrescrever muitas configurações. No exemplo desse artigo, basicamente restringimos o acesso as propriedades de um objeto, mas podemos fazer muito mais. Como esse recurso foi lançado na versão ES6, podemos considerar que os navegadores atuais já dão suporte a esse recurso, porém, em caso de dúvida, é sempre bom checar no caniuse.com.

Espero que tenham aproveitado esse artigo, até o próximo.

Discussion (0)