Introdução – JavaScript | MDN (mozilla.org)
CAPÍTULO 16
Cortesia da Startup Valeon (https://valedoacoonline.com.br/)
Meta programação
Começando com ECMAScript 6, o JavaScript ganha suporte para os objetos Proxy
e Reflect
, permitindo você interceptar e definir o comportamento personalizado para operações fundamentais da linguagem (por exemplo, pesquisa de propriedade, atribuição, enumeração, invocação de função, etc). Com a ajuda destes dois objetos você será capaz de programar a nível meta em JavaScript.
Proxies
Introduzido em ECMAScript 6, objetos Proxy
permitem que você intercepte determinadas operações e implementar comportamentos personalizados. Por exemplo, receber uma propriedade em um objeto:
varhandler
={
get:
function(target, name){
return
name
intarget
?target
[name
]:
42;
}};
varp
=new
Proxy({},
handler
);
p
.a
=1;
console
.log(p
.a
,p
.b
);// 1, 42
O objeto Proxy define um target (um objeto vazio aqui) e um objeto handler em que um get
trap é implementado. Aqui, um objeto que está em proxy não retornará indefinido quando receber propriedades indefinidas, mas, ao contrário, retornar o número 42.
Exemplos adicionais estão disponíveis na página de referência de Proxy
.
Terminologia
Os seguintes termos são usados quando se fala sobre a funcionalidade de proxies.
Espaço reservado de objeto que contenha traps.
traps
Os métodos que fornecem acesso de propriedade. Isto é análogo ao conceito de traps em sistemas operacionais.
target
Objeto que o proxy está virtualizando. Ele é frequentemente usado como backend de armazenamento para o proxy. Invariantes (semânticas que permanecem inalteradas) relativas a objetos que não podem ser extendidos ou propriedades que não podem ser configuradas são comparadas com o target.
invariantes
Semânticas que permanecem inalteradas na execução de operações personalizadas são chamados de invariantes. Se você violar as invariantes de um manipulador, um TypeError
será lançado.
Handlers e traps
A tabela a seguir resume as traps disponíveis aos objetos do tipo Proxy. Veja as páginas de referência (en-US) para explicações detalhadas e exemplos.
Handler / trap | Interceptions | Invariants |
handler.getPrototypeOf() (en-US) | Object.getPrototypeOf() Reflect.getPrototypeOf() (en-US)__proto__ Object.prototype.isPrototypeOf() instanceof | O método getPrototypeOf deve retornar um object ou null .Se target não puder ser extendido, o método Object.getPrototypeOf(proxy) deve retornar o mesmo valor que Object.getPrototypeOf(target) . |
handler.setPrototypeOf() (en-US) | Object.setPrototypeOf() Reflect.setPrototypeOf() (en-US) | Se target não puder ser extendido, o parâmetro prototype dever ter o mesmo valor que Object.getPrototypeOf(target) . |
handler.isExtensible() (en-US) | Object.isExtensible() Reflect.isExtensible() (en-US) | Object.isExtensible(proxy) deve retornar o mesmo valor que Object.isExtensible(target) . |
handler.preventExtensions() (en-US) | Object.preventExtensions() Reflect.preventExtensions() (en-US) | Object.preventExtensions(proxy) retorna true somente se Object.isExtensible(proxy) retornar false . |
handler.getOwnPropertyDescriptor() (en-US) | Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor() (en-US) | getOwnPropertyDescriptor deve retornar um object ou undefined .Uma propriedade não pode ser descrita como não existente se ela existir como uma propriedade própria e não configurável do objeto alvo.Uma propriedade não pode ser relatada como inexistente, se existir como uma propriedade própria do objeto de destino e o objeto de destino não for extensível.Uma propriedade não pode ser relatada como existente, se não existir como uma propriedade própria do objeto de destino e o objeto de destino não for extensível.Uma propriedade não pode ser relatada como não configurável, se não existir como uma propriedade própria do objeto de destino ou se existir como uma propriedade própria configurável do objeto de destino.O resultado de Object.getOwnPropertyDescriptor(target) pode ser aplicado ao objeto de destino usando Object.defineProperty e não emitirá uma exceção. |
handler.defineProperty() (en-US) | Object.defineProperty() Reflect.defineProperty() | Uma propriedade não pode ser adicionada se o objeto de destino não for extensível.Uma propriedade não pode ser adicionada como ou modificada para não ser configurável, se não existir como uma propriedade própria não configurável do objeto de destino.Uma propriedade pode não ser não configurável, se existir uma propriedade configurável correspondente do objeto de destino.Se uma propriedade tiver uma propriedade de objeto de destino correspondente Object.defineProperty(target, prop, descriptor) não lançará uma exceção.No modo estrito, um valor de retorno false do manipulador defineProperty manipulador lançará um TypeError exceção. |
handler.has() (en-US) | Property query: foo in proxy Inherited property query: foo in Object.create(proxy) Reflect.has() (en-US) | Uma propriedade não pode ser relatada como inexistente, se existir como uma propriedade própria não configurável do objeto de destino.Uma propriedade não pode ser relatada como inexistente, se existir como uma propriedade própria do objeto de destino e o objeto de destino não for extensível. |
handler.get() (en-US) | Property access: proxy[foo] and proxy.bar Inherited property access: Object.create(proxy)[foo] Reflect.get() (en-US) | O valor relatado para uma propriedade deve ser igual ao valor da propriedade do objeto de destino correspondente se a propriedade do objeto de destino for uma propriedade de dados não gravável e não configurável.O valor relatado para uma propriedade deve ser indefinido se a propriedade do objeto de destino correspondente for uma propriedade acessora não configurável que tenha sido indefinida como seu atributo [[Get]]. |
handler.set() (en-US) | Property assignment: proxy[foo] = bar and proxy.foo = bar Inherited property assignment: Object.create(proxy)[foo] = bar Reflect.set() | Não é possível alterar o valor de uma propriedade para ser diferente do valor da propriedade do objeto de destino correspondente se a propriedade do objeto de destino correspondente for uma propriedade de dados não gravável e não configurável.Não é possível definir o valor de uma propriedade se a propriedade do objeto de destino correspondente for uma propriedade acessadora não configurável que tenha undefined como seu atributo [[Set]].No modo estrito, um valor de retorno false do manipulador set lançará uma exceção TypeError }. |
handler.deleteProperty() (en-US) | Property deletion: delete proxy[foo] and delete proxy.foo Reflect.deleteProperty() (en-US) | Uma propriedade não pode ser excluída, se existir como uma propriedade própria não configurável do objeto de destino. |
handler.enumerate() | Property enumeration / for…in: for (var name in proxy) {...} Reflect.enumerate() | O método enumerate deve retornar um objeto. |
handler.ownKeys() (en-US) | Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Object.keys() Reflect.ownKeys() (en-US) | O resultado de ownKeys é uma lista.O tipo de cada elemento da lista de resultados é String ou Symbol .A lista de resultados deve conter as chaves de todas as propriedades próprias não configuráveis do objeto de destino.Se o objeto de destino não for extensível, a Lista de resultados deverá conter todas as chaves das próprias propriedades do objeto de destino e nenhum outro valor. |
handler.apply() (en-US) | proxy(..args) Function.prototype.apply() and Function.prototype.call() Reflect.apply() | Não há invariantes para o método handler.apply |
handler.construct() (en-US) | new proxy(...args) Reflect.construct() | O resultado deve ser um Object . |
Proxy Revogável
O método Proxy.revocable()
(en-US) é utilizado para criar um objeto Proxy revogável. Isso significa que o proxy pode ser revogado através da função revoke
, desligando-o. Depois disso, qualquer operação com o proxy lançará um TypeError
.
varrevocable
=Proxy
.revocable({},{
get:
function(target, name)
{
return
"[["
+
name
+"]]";
}
});
varproxy
=revocable
.proxy
;
console
.log(proxy
.foo
);// "[[foo]]"
revocable
.revoke();
console
.log(proxy
.foo
);// TypeError é lançado
proxy
.foo
=1
// TypeError novamente
deleteproxy
.foo
;// ainda um TypeError
typeof proxy
// "object", typeof não desencadeia nenhuma trap
Reflexão
Reflect
é um objeto embutido que contém métodos que permitem a criação de operações interceptáveis em JavaScript. Os métodos são iguais àqueles de proxy handlers (en-US). Reflect
não é um objeto do tipo function.
Reflect
auxilia no encaminhamento de operações padrão do handler para o target.
Reflect.has()
(en-US), por exemplo, tem o mesmo efeito prático que o operador in, com a facilidade de ser utilizado como uma função:
Reflect
.has(Object
,"assign");
// true
Uma função apply
melhorada
Em ES5, você normalmente utiliza o método Function.prototype.apply()
para invocar uma função com um dado valor para this
e arguments
fornecido como um array (ou um objeto parecido com um array).
Function.prototype
.apply.call(Math
.floor
,undefined,
[1.75]);
Com Reflect.apply
essa operação se torna menos verbosa e mais fácil de compreender:
Reflect.apply(Math
.floor
,undefined,
[1.75]);
// 1;
Reflect.apply(String
.fromCharCode
,undefined,
[104,
101,
108,
108,
111]);
// "hello"
Reflect.apply(RegExp.prototype
.exec
,/ab/,
["confabulation"]).
index
;
// 4
Reflect.apply("".charAt
,"ponies",
[3]);
// "i"
Verificando se a definição da propriedade obteve sucesso
Com Object.defineProperty
, a qual returna um object
em caso de sucesso ou lança um TypeError
em caso contrário, você utilizaria um bloco try...catch
para capturar qualquer erro que tenha ocorrido ao definir uma propriedade. Devido ao fato de Reflect.defineProperty
retornar um status do tipo Boolean
, você pode simplesmente utilizar aqui um bloco if...else
:
if(
Reflect
.defineProperty(target
,property
,attributes
)){
// success
}else
{
// failure
}