Entendendo Proxies e Closures utilizando Vue.js

Bernardo Hazin
Mesa Mobile Thinking
5 min readApr 3, 2020
Photo by Maarten van den Heuvel on Unsplash

Desde que comecei a desenvolver em Vue.js, uma das funcionalidades que mais me chamaram a atenção logo de cara foram os Observadores (Watchers). Um Observador no Vue.js nada mais é do que uma propriedade que te permite observar e reagir a alterações de variáveis definidas no contexto da sua aplicação de uma forma simples e intuitiva. Para esclarecer um pouco mais, vou demonstrar em um exemplo:

No exemplo acima a propriedade “hey definida no “data” está sendo monitorada, ou seja toda vez que seu valor for alterado, no caso do exemplo no clique do botão, o método observador de mesmo nome definido no “watch” será executado exibindo uma mensagem de alteração. Algo simples, mas que pode ser de grande ajuda na hora de desenvolver algumas tarefas, como por exemplo um campo de busca exibido no exemplo abaixo:

Link para o pen

Neste caso, na medida que o usuário preenche o campo de busca o valor de “search” é atualizado, executando em sequência uma chamada para o método observador. Para este exemplo não é bem o que queremos, pois se pensarmos nesse campo como uma busca direta a uma API, uma série de requisições seriam feitas para o servidor em um pequeno intervalo de tempo. Por isso que para este exemplo adicionei a função debounce, que atrasa a chamada do método com base no tempo de 800ms definido por mim entre sua última chamada e a sua execução.

Desta forma, a busca só será realizada 0.8s depois que o usuário parou de digitar. Uma solução bem simples e abstraída para nós através do framework e outras bibliotecas.

Mas como isso funciona na prática?

É interessante fazer este tipo de questionamento, pois ao entender como isso de fato funciona nos deparamos com alguns conceitos básicos de Javascript e programação importantes de se ter em mente como “Proxies” e “Closures”.

Então vamos reimplementar essa mesma funcionalidade de busca apenas utilizando Javascript na sua versão ES6 para ver esses conceitos de perto.

Para isso, vamos começar definindo o nosso documento HTML junto com o campo que queremos observar:

A partir daqui já podemos começar a capturar o valor da busca do usuário, associá-la a nossa propriedade “search” e definir o método a ser executado a cada iteração do usuário.

Agora basta executar uma chamada para o método observador toda vez que a propriedade “search” for alterada. Podemos facilmente fazer isso utilizando um Proxy.

Um Proxy no Javascript nada mais é do que um encapsulamento das propriedades de alguma entidade (objetos, arrays, funções, etc…), que nos permite reescrever alguns comportamentos fundamentais, tais como o “set”, que no caso de um objeto que funciona para alterar o valor de alguma propriedade e que no nosso exemplo pode ser utilizado para fazer a chamada para o método “search.

O construtor do Proxy recebe dois argumentos (target, handler): o primeiro seria o objeto que queremos encapsular e o segundo um outro objeto contendo as propriedades que queremos reescrever. No nosso exemplo teríamos algo assim:

Note que o nome search é utilizado como nome da propriedade tanto no data quanto no watch para facilitar a chamada para o método, já que a seu valor é passado como segundo argumento no set.

Agora que já estamos monitorando as alterações do usuário, só nos falta de fato efetuar a busca. Para que ela fique completa, ainda precisamos da função debounce mencionada anteriormente.

Para implementar um debouncer, precisamos entender o conceito de closures. Basicamente, podemos chamar de closure uma função que se “lembra” do seu escopo de criação, como se ela captura-se o momento em que foi criada, guardando as variáveis e métodos definidos em seu escopo, como no exemplo abaixo:

No código acima, sempre que a função generateClosure for executada, o valor passado como argumento será gravado na função de retorno, que no caso passa a ser um closure, pois se “lembra” do argumento “prefix” definido em sua criação.

Com isso em mente, podemos pensar na lógica da função debounce, que consiste em só executar uma determinada função passada por argumento após um determinado tempo. Para isso, podemos utilizar um setTimeout como no exemplo abaixo:

Analisando o código acima, toda vez que a função debounce for executada, ela retornará um closure, que em sua criação possui uma referência a um timeout definido em um escopo externo. Dessa forma, podemos reiniciar o contador toda vez que ocorrer uma chamada para a função e apenas executá-la utilizando o Function.prototype.apply após um determinado tempo, passando o contexto de criação do debouncer e os argumentos através da keyword arguments.

Dessa forma temos todas as ferramentas em mãos para finalizar a nossa funcionalidade!

O resultado:

Link para o código

Em conclusão, é interessante entender a “mágica” pro trás dos frameworks e bibliotecas, que no final são códigos bem tangíveis e acessíveis e te ajudam a pensar melhor em como escrever seu código e o que está acontecendo em sua execução.

E você, já chegou a usar algumas dessas funcionalidades? Deixa um comentário!

Agradeço pela leitura e espero ter ajudado!

--

--