Aplicar funções JavaScript permite-lhe adicionar lógica comum a funções que não controla, como funções nativas e externas. Muitas bibliotecas JavaScript, como os agentes do TrackJS, necessitam de envolver funções externas para fazer o seu trabalho. A adição de wrappers permite-nos ouvir Telemetria, erros e logs no seu código, sem necessitar de chamar explicitamente a nossa API.
Pode querer embrulhar uma função para adicionar instrumentação ou lógica de depuração temporária. Você também pode alterar o comportamento de uma biblioteca externa sem precisar modificar o código fonte.
Basic Function Wrapping
Porque o JavaScript é maravilhosamente dinâmico, podemos embrulhar uma função simplesmente redefinindo a função com algo novo. Por exemplo, considere este embrulho de myFunction
:
Neste exemplo trivial, embrulhamos o original myFunction
e adicionamos uma mensagem de registo. Mas há muitas coisas que não tratámos:
- Como passamos os argumentos da função?
- Como mantemos o escopo (o valor de
this
)? - Como obtemos o valor de retorno?
- E se acontecer um erro?
Para lidar com estas coisas, precisamos de ser um pouco mais espertos no nosso embrulho.
Notem que não estamos apenas a invocar a função neste exemplo, mas call
-ingindo-a com o valor para this
e argumentos a
, b
, e c
. O valor de this
será passado de onde quer que você anexe sua função embalada, Window
neste exemplo.
>
Também rodeamos toda a função num bloco de try/catch
para que possamos invocar a lógica personalizada em caso de erro, rethrow it, ou retornar um valor padrão.
Advanced Function Wrapping
O exemplo básico de wrapping funcionará 90% do tempo, mas se estiver a construir bibliotecas partilhadas, como os agentes TrackJS, isso não é suficientemente bom! Para embrulhar as nossas funções como um profissional, existem alguns casos de bordas com os quais devemos lidar:
- E que tal argumentos não declarados ou desconhecidos?
- Como fazemos corresponder a assinatura da função?
- Como espelhamos as propriedades da função?
Existem 3 subtis mas importantes alterações. Primeiro (#1), nós nomeamos a função. Parece redundante, mas o código do utilizador pode verificar o valor de function.name
, por isso é importante manter o nome quando se faz o wrapping.
A segunda alteração (#2) está em como chamamos a função wrapped, usando apply
em vez de call
. Isto permite-nos passar por um objecto arguments
, que é um objecto tipo array-like de todos os argumentos passados para a função em tempo de execução. Isto permite-nos suportar funções que podem ter um número indefinido ou variável de argumentos.
Com apply
, não precisamos dos argumentos a
, b
, e c
definidos na assinatura da função. Mas continuando a declarar os mesmos argumentos que a função original, mantemos a aridade da função. Ou seja, Function.length
devolve o número de argumentos definidos na assinatura, e isto irá espelhar a função original.
A alteração final (#3) copia quaisquer propriedades especificadas pelo utilizador da função original para o nosso wrapping.
Limitações
Este wrapping é completo, mas existem sempre limitações no JavaScript. Especificamente, é difícil embrulhar corretamente uma função com um protótipo não-padrão, como um construtor de objetos. Este é um caso de uso melhor resolvido por herança.
Em geral, alterar o protótipo de uma função é possível, mas não é uma boa ideia. Existem sérias implicações de performance e efeitos colaterais não intencionais na manipulação de protótipos.
Respeitar o Ambiente
O embrulho da função dá-lhe muita potência para instrumentar e manipular o ambiente JavaScript. Você tem a responsabilidade de manusear esse poder de forma sensata. Se está a construir invólucros funcionais, certifique-se de respeitar o utilizador e o ambiente em que está a operar. Pode haver outros wrappers no local, outros ouvintes anexados para eventos e expectativas sobre as APIs de funções. Ande levemente e não quebre o código externo.