Reflection em Javascript

Lucas Santos
Training Center
Published in
5 min readOct 30, 2017

O que é Reflection? Você já ouviu falar em usar o seu próprio código para mexer nele mesmo?

Por que reflection?

Muitas linguagens já oferecem uma API de reflection (como o C# ou o Python) há muito tempo. O Javascript em si dificilmente vai ter uma necessidade muito grande por uma API deste tipo (até porque ele é uma linguagem dinâmica). A questão é que, com o ES6, a linguagem ganhou alguns outros pontos extensíveis onde o desenvolvedor ganha acesso a locais que, antes, eram apenas internos à linguagem, como o Proxy.

Se você for pensar bem, provavelmente vai falar “mas o ES5 já tinha reflection”, mesmo que nenhum dos métodos eram realmente chamados de reflection, nem pela comunidade e nem pela especificação. Alguns métodos como Array.isArray, Object.getOwnPropertyDescriptor, e até o Object.keys são exemplos que você acharia categorizado como reflection em outras linguagens. O built-in chamado Reflect vai, futuramente, agregar outros métodos desta categoria, como não deveria ser diferente…

Reflect API

Como já mencionamos antes, o built-in chamado Reflect é um objeto estático (assim como o Math), ou seja, você não pode usar um new com ele.

Indo mais a fundo, o Reflect é uma coleção de proxies onde cada método estático dentro deste objeto é mapeado para uma trap, o que, essencialmente, diz que cada trap do proxy tem um método correspondente no Reflect.

Abaixo vou dar alguns exemplos muito interessantes do uso dessa API de reflection.

Valores de retorno no Reflect vs reflection por Object

Os métodos equivalentes ao Reflect em Object também provém uma maneira mais significativa e com mais significado de retornar valores. Por exemplo, o método Reflect.defineProperty retorna um boolean indicando se a propriedade foi ou não foi definida com sucesso.

Mas, a contraparte Object.defineProperty retorna o objeto que foi passado como primeiro argumento… O que não é nada útil para o programador.

Para mostrar isso, vamos para um snippet mostrando um pouco do Object.defineProperty:

O que é meio o oposto de uma maneira mais natural de se programar com o Reflect.defineProperty:

Assim podemos não só evitar o bloco try/catch, mas também programar de um jeito muito mais legível.

Keyword vs Método

Alguns destes métodos existentes no Reflect provém uma funcionalidade que, antes, só estavam disponíveis através de keywords. Um exemplo clássico é Reflect.deleteProperty(objeto, chave), que é um exemplo do uso da keyword delete, como em delete objeto[chave].

O grande problema disso é que keywords não produzem retorno, de forma que você não tem a menor ideia do que está acontecendo. Assumimos simplesmente que as keywords funcionam na linguagem, e geralmente isso é verdade, quando não funcionam temos um lindo Error na nossa cara.

Então se você queria usar alguma função que resultava em um delete você precisaria criar um método que fosse um wrapper do delete e retornar o seu próprio valor:

Atualmente já temos métodos que podem trabalhar de forma mais inteligente:

Número arbitrário de argumentos

Como você faria para criar um novo objeto chamado Foo através de um new Foo passando uma lista arbitrária de argumentos no ES5? Não dava para fazer diretamente, certo? E mesmo que você consiga fazer, basicamente teríamos que escrever a bíblia, isto porque temos que criar um objeto intermediário que vai receber todos os argumentos como um array; Depois pegamos este objeto e fazemos o construtor dele retornar o resultado de aplicar o construtor original do objeto que você queria utilizar o .apply, fácil né? (ou talvez não):

Bom, agora só precisamos usar oapply, fica bem mais fácil:

Em algum momento da história, alguém tinha tinha que ter uma EXCELENTE razão para passar por tudo isso. Hoje com ES6 é muito menos insano aplicar propriedades arbitrárias, uma delas é o spread operator:

new Dominus(...args)

Uma outra alternativa é usar o Reflect:

Reflect.construct(Dominus, args)

Aplicação de funções, do jeito certo

Antigamente, no ES5, quando queriamos chamar uma função ou método com um número aleatório de argumentos, geralmente tínhamos que usar o .apply, passando um contexto que era o this:

fn.apply(ctx, [1,2,3])

Ou, se tivéssemos medo que o apply não fosse exatamente o que queríamos chamar (isto é pra todo mundo que sobrescreve o runtime), mas uma função própria definida pelo objeto, poderíamos ter ainda mais certeza usando call:

Function.prototype.apply.call(fn, ctx, [1,2,3])

Podemos substituir tudo isso pelo spread operator, como uma alternativa para o .apply:

fn(...[1,2,3])

Isso não vai resolver seu problema quando precisamos definir um contexto. Aí podemos utilizar o modo que fizemos com o prototype da função, o que é meio verboso, ou então utilizar Reflect.apply(fn, ctx, args) que é bem mais simples.

Obviamente um dos casos de uso mais clássicos para o apply é o comportamento padrão de uma trap de um proxy.

Comportamentos padrões de proxies

No topo deste artigo eu disse como as traps são mapeadas para métodos do Reflect. Mas não falamos nada sobre o fato de que as interfaces delas também combinam, ou seja, tanto os argumentos quanto os valores de retorno batem um com o outro.

Falando em código, isso significa que você pode fazer uma coisa mais ou menos assim para poder obter um comportamento padrão de um get em um proxy:

Não tem nada falando que você não pode deixar essa handler ainda mais simples. Claro que se esse for o caso é melhor você nem usar um objeto encapsulando esta trap…

let handler = { get: Reflect.get }

A coisa importante de notar aqui é que você pode setar uma trap nas suas handlers para proxies, misturar uma funcionalidade personalizada que acaba mandando um console.log, então depois, no caso padrão você pode usar o mesmo modelo de cima só que em uma linha só: return Reflect[nomeTrap](...argumentos).

Conclusão

Reflection pode ser uma coisa um pouco complicada de entender, mas com o tempo e estudo, com certeza vale a pena se aprofundar mais em saber como o seu próprio código consegue conversar e “se ver no espelho”. A reflect API veio para tornar a vida do programador muito mais fácil!

Não deixe de acompanhar mais do meu conteúdo no meu blog e se inscreva na newsletter para receber notícias semanais!

Se você quer dar uma estudada mais a fundo dê uma olhada nesses links:

--

--

Lucas Santos
Training Center

Brazilian Programmer, caught between the black screen and rock n' roll 🤘 — Senior software Engineer