O que o objeto Reflect faz em JavaScript?

87

Eu vi um esboço em branco no MDN um tempo atrás para o Reflectobjeto em javascript, mas não consigo encontrar nada no Google. Hoje encontrei este http://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-object e parece semelhante ao objeto Proxy, independentemente do domínio e da funcionalidade do carregador.

Basicamente, não sei se esta página que encontrei apenas explica como implementar o Reflect ou se simplesmente não consigo entender seu texto. Alguém poderia me explicar em geral quais são os métodos de Reflectfazer?

Por exemplo, na página que encontrei diz que a chamada Reflect.apply ( target, thisArgument, argumentsList ) irá "Retornar o resultado da chamada do método interno [[Call]] de destino com os argumentos thisArgument e args." mas como isso é diferente de apenas ligar target.apply(thisArgument, argumentsList)?

Atualizar:

Graças a @Blue, encontrei esta página no wiki http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect que, pelo que sei, diz que o objeto refletido fornece versões de método de todos as ações que podem ser interceptadas por proxies para facilitar o encaminhamento. Mas isso parece um pouco estranho para mim, já que não vejo como é totalmente necessário. Mas parece fazer um pouco mais do que isso, particularmente o par que diz, double-liftingmas que aponta para a antiga especificação de proxy /

Jim Jones
fonte
1
A especificação diz "O objeto Reflect é um único objeto comum.", No meu entendimento Reflecté apenas um contêiner para objetos Realme Loader, mas também não sei o que estes fazem.
simonzack
Obrigado :), pela página que criei um link (não sei se é legítimo), cada Realm é seu próprio "contexto de script java" e um carregador carrega Realms como módulos ou algo assim, com base nas semelhanças entre refletir e proxy e o fato de que tipo de proxy de "sobrecarga" de funcionalidade embutida pode Reflect.Loadere Reflect.Realmtem algo a ver com a funcionalidade de módulo de sobrecarga?
Jim Jones
1
Parece que é uma 'classe estática' (como JSON) com métodos estáticos: isExtensible, ownKeysetc. Em ES 6, com aulas reais, isto é útil para descobrir mais sobre uma classe ( targetem 16.1.2 eu acho).
Rudie

Respostas:

129

ATUALIZAÇÃO 2015: Como apontado por 's resposta , agora que ES6 (ECMAScript 2015) foi finalizado, a documentação mais apropriado já está disponível:


Resposta original (para compreensão (histórica) e exemplos extras) :

O Reflection proposalparece ter progredido para a Especificação ECMAScript 6 do Projeto . Este documento atualmente descreve os Reflectmétodos do -objeto e afirma apenas o seguinte sobre o Reflectpróprio -objeto:

O objeto Reflect é um único objeto comum.

O valor do slot interno [[Prototype]] do objeto Reflect é o objeto de protótipo padrão interno do objeto (19.1.3).

O objeto Reflect não é um objeto de função. Ele não tem um método interno [[Construct]]; não é possível usar o objeto Reflect como um construtor com o novo operador. O objeto Reflect também não possui um método interno [[Call]]; não é possível invocar o objeto Reflect como uma função.

No entanto, há uma breve explicação sobre seu propósito no ES Harmony :

O módulo “@reflect” tem várias finalidades:
  • Agora que temos módulos, um módulo “@reflect” é um lugar mais natural para muitos dos métodos de reflexão previamente definidos em Object. Para fins de compatibilidade com versões anteriores, é improvável que os métodos estáticos em Object desapareçam. No entanto, novos métodos provavelmente devem ser adicionados ao módulo “@reflect” ao invés do construtor Object.
  • Um lar natural para proxies, evitando a necessidade de uma ligação de proxy global.
  • A maioria dos métodos neste módulo mapeia um a um em armadilhas de proxy. Os manipuladores de proxy precisam desses métodos para encaminhar convenientemente as operações, conforme mostrado abaixo.



Portanto, o Reflectobjeto fornece várias funções de utilitário, muitas das quais parecem se sobrepor aos métodos ES5 definidos no objeto global.

No entanto, isso não explica realmente quais problemas existentes isso pretende resolver ou quais funcionalidades são adicionadas. Suspeitei que isso pudesse ser corrigido e, de fato, as especificações de harmonia acima se vinculam a uma 'implementação aproximada não normativa desses métodos' .

Examinar esse código pode dar (mais) idéias sobre seu uso, mas felizmente também há um wiki que descreve uma série de razões pelas quais o objeto Reflect é útil :
(Copiei (e formatei) o seguinte texto para referência futura daquele fonte pois são os únicos exemplos que consegui encontrar. Além disso, fazem sentido, já têm uma boa explicação e tocam no applyexemplo da pergunta .)


Valores de retorno mais úteis

Muitas operações em Reflectsão semelhantes às operações ES5 definidas em Object, como Reflect.getOwnPropertyDescriptore Reflect.defineProperty. No entanto, enquanto Object.defineProperty(obj, name, desc)irá retornar objquando a propriedade foi definida com sucesso, ou lançar um TypeErrorcaso contrário, Reflect.defineProperty(obj, name, desc)é especificado para simplesmente retornar um booleano que indica se a propriedade foi definida com sucesso ou não. Isso permite que você refatore este código:

try {
  Object.defineProperty(obj, name, desc);
  // property defined successfully
} catch (e) {
  // possible failure (and might accidentally catch the wrong exception)
}

Para isso:

if (Reflect.defineProperty(obj, name, desc)) {
  // success
} else {
  // failure
}

Outros métodos que retornam esse status de sucesso booleano são Reflect.set(para atualizar uma propriedade), Reflect.deleteProperty(para excluir uma propriedade), Reflect.preventExtensions(para tornar um objeto não extensível) e Reflect.setPrototypeOf(para atualizar o link de protótipo de um objeto).


Operações de primeira classe

No ES5, a maneira de detectar se um objeto objdefine ou herda um determinado nome de propriedade é escrever (name in obj). Da mesma forma, para excluir uma propriedade, usa-se delete obj[name]. Embora a sintaxe dedicada seja boa e curta, também significa que você deve envolver explicitamente essas operações em funções quando quiser passar a operação como um valor de primeira classe.

Com Reflect, essas operações são prontamente definidas como funções de primeira classe:
Reflect.has(obj, name)é o equivalente funcional de (name in obj)e Reflect.deleteProperty(obj, name)é uma função que faz o mesmo quedelete obj[name].


Aplicação de função mais confiável

No ES5, quando se deseja chamar uma função fcom um número variável de argumentos empacotados como uma matriz argse vincular o thisvalor a obj, pode-se escrever:

f.apply(obj, args)

No entanto, fpode ser um objeto que define intencionalmente ou não seu próprio applymétodo. Quando você realmente deseja ter certeza de que a applyfunção integrada é chamada, normalmente escreve-se:

Function.prototype.apply.call(f, obj, args)

Não é apenas prolixo, mas rapidamente se torna difícil de entender. Com o Reflect, agora você pode fazer uma chamada de função confiável de uma forma mais curta e fácil de entender:

Reflect.apply(f, obj, args)


Construtores de argumento variável

Imagine que você deseja chamar uma função construtora com um número variável de argumentos. No ES6, graças à nova sintaxe difundida, será possível escrever códigos como:

var obj = new F(...args)

No ES5, isso é mais difícil de escrever, porque só se pode usar F.applyou F.callchamar uma função com um número variável de argumentos, mas não há F.constructfunção para newa função com um número variável de argumentos. Com Reflect, agora é possível escrever, em ES5:

var obj = Reflect.construct(F, args)


Comportamento de encaminhamento padrão para armadilhas de proxy

Ao usar Proxyobjetos para encapsular objetos existentes, é muito comum interceptar uma operação, fazer algo e, em seguida, "fazer a coisa padrão", que normalmente é aplicar a operação interceptada ao objeto encapsulado. Por exemplo, digamos que eu queira simplesmente registrar todos os acessos de propriedade a um objeto obj:

var loggedObj = new Proxy(obj, {
  get: function(target, name) {
    console.log("get", target, name);
    // now do the default thing
  }
});

As APIs Reflecte foram projetadas em conjunto , de forma que para cada armadilha, existe um método correspondente que "faz a coisa padrão". Portanto, sempre que você quiser "fazer o padrão" dentro de um manipulador Proxy, a coisa correta a fazer é sempre chamar o método correspondente no objeto:ProxyProxyReflectReflect

var loggedObj = new Proxy(obj, {
  get: function(target, name) {
    console.log("get", target, name);
    return Reflect.get(target, name);
  }
});

O tipo de retorno dos Reflectmétodos tem garantia de compatibilidade com o tipo de retorno das Proxyarmadilhas.


Controlar esta ligação de acessores

No ES5 é bastante fácil fazer um acesso de propriedade genérico ou atualização de propriedade. Por exemplo:

var name = ... // get property name as a string
obj[name] // generic property lookup
obj[name] = value // generic property update

Os métodos Reflect.gete Reflect.setpermitem que você faça a mesma coisa, mas, adicionalmente, aceitam como último argumento opcional um receiverparâmetro que permite definir explicitamente a this-binding quando a propriedade que você obtém / define é um acessador:

var name = ... // get property name as a string
Reflect.get(obj, name, wrapper) // if obj[name] is an accessor, it gets run with `this === wrapper`
Reflect.set(obj, name, value, wrapper)

Isso é ocasionalmente útil quando você está empacotando obje deseja que qualquer envio automático dentro do acessador seja redirecionado para seu wrapper, por exemplo, se objfor definido como:

var obj = {
  get foo() { return this.bar(); },
  bar: function() { ... }
}

Chamar Reflect.get(obj, "foo", wrapper)fará com que a this.bar()chamada seja redirecionada para wrapper.


Evite legado __proto__

Em alguns navegadores, __proto__é definido como uma propriedade especial que dá acesso ao protótipo de um objeto. ES5 padronizou um novo método Object.getPrototypeOf(obj)para consultar o protótipo. Reflect.getPrototypeOf(obj)faz exatamente o mesmo, exceto que Reflecttambém define um correspondente Reflect.setPrototypeOf(obj, newProto)para definir o protótipo do objeto. Esta é a nova maneira compatível com ES6 de atualizar o protótipo de um objeto.
Note-se que: setPrototypeOf também existe emObject (como corretamente apontado por Knu 's comentário )!


EDIT:
Nota lateral (endereçando comentários ao Q): Há uma resposta curta e simples em 'Q: Módulos ES6 vs. Importações de HTML' que explica Realmse Loaderobjeta.

Outra explicação é oferecida por este link :

Um objeto de domínio abstrai a noção de um ambiente global distinto, com seu próprio objeto global, cópia da biblioteca padrão e "intrínsecos" (objetos padrão que não estão vinculados a variáveis ​​globais, como o valor inicial de Object.prototype).

Web extensível : este é o equivalente dinâmico de uma mesma origem <iframe>sem DOM.

Vale a pena mencionar: tudo isso ainda está em projeto, esta não é uma especificação gravada na pedra! É ES6, portanto, mantenha a compatibilidade do navegador em mente!

Espero que isto ajude!

GitaarLAB
fonte
@Spencer Killen: Bem ... essa resposta apontou seus pensamentos na direção certa e explicou como isso se relaciona com a diferença entre Reflect.applye target.apply? Ou o que devo acrescentar antes que a recompensa termine?
GitaarLAB
2
setPrototypeOf também existe em Object.
Knu
1
Observe que usar Reflect.getcomo implementação padrão para proxy get não funciona bem se você estiver fazendo proxy de um objeto com propriedades de protótipo. Ele apenas reclama que não funciona. No entanto, se você usar Reflect.get(target, property)sem passar o receiver, ele funcionará.
CMCDragonkai
Meus testes em que você sempre acessa propriedades por meio do proxy, resultam em uma situação em que o targeté o destino original que o proxy envolve, enquanto receiveré o próprio proxy. Mas, novamente, isso pode ser diferente se você conseguir acessar as propriedades de maneira diferente.
CMCDragonkai
5

Indo pelo rascunho do documento encontrado no wiki,

http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts

Pegamos a linha sobre "objeto comum único" que ela esclarece no rascunho. Ele também contém as definições de função.

O wiki deve ser confiável, pois você pode encontrar um link para ele no site do emcascript

http://www.ecmascript.org/dev.php

No entanto, encontrei o primeiro link do google e não tive sorte em encontrá-lo pesquisando diretamente no wiki.

Azul
fonte
Obrigado, as etapas executadas quando cada método é chamado listado na especificação oficial parecem praticamente iguais às do meu link, mas ainda não consigo descobrir o que cada método faz quando é chamado, por exemplo, em Reflect. Aplique as etapas listadas 4. Execute a operação abstrata PrepareForTailCall. 5. Retorne o resultado da chamada do método interno [[Call]] do destino com os argumentos thisArgument e args ------- o que isso significa?
Jim Jones
Meu melhor palpite, olhando para ele, é que ele está referenciando a recursão da cauda na etapa 4 e a etapa 5 é uma referência à função de protótipo. Parece que a ideia geral é verificar se o método apply pode ser executado no que é aplicado (etapas 1-2), manipular o erro (etapa 3) e, em seguida, chamar a função apply está sendo executada (etapas 4-5). Meu melhor palpite, ao examinar a documentação, é que o objetivo do módulo Reflect é quando você executa uma funcionalidade que requer alguma forma de introspecção do objeto. O uso de call também é provavelmente o motivo pelo qual "não tem um método interno [[Call]]".
Azul de