JavaScript função aliasing não parece trabalho
-
06-07-2019 - |
Pergunta
Eu estava lendo esta questão e queria tentar o método apelido em vez do método da função-wrapper, mas eu não conseguia fazê-lo funcionar em qualquer Firefox 3 ou 3.5beta4, ou Google Chrome, tanto em suas janelas de depuração e em uma página de teste web.
Firebug:
>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">
Se eu colocá-lo em uma página web, a chamada para myAlias ??me dá esse erro:
uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///[...snip...]/test.html :: <TOP_LEVEL> :: line 7" data: no]
Chrome (com >>> 's inseridos para maior clareza):
>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias('item1')
TypeError: Illegal invocation
>>> document.getElementById('item1')
<div id=?"item1">?
E na página de teste, eu recebo o mesmo "invocação ilegal".
Estou fazendo algo errado? Qualquer pessoa pode reproduzir este?
Além disso, curiosamente, eu apenas tentei e ele funciona no IE8.
Solução
Você tem que ligar esse método para o objeto documento. Look:
>>> $ = document.getElementById
getElementById()
>>> $('bn_home')
[Exception... "Cannot modify properties of a WrappedNative" ... anonymous :: line 72 data: no]
>>> $.call(document, 'bn_home')
<body id="bn_home" onload="init();">
Quando você está fazendo um simples apelido, a função é chamado no objeto global, não no objeto de documento. Usam uma técnica chamada tampas para corrigir isso:
function makeAlias(object, name) {
var fn = object ? object[name] : null;
if (typeof fn == 'undefined') return function () {}
return function () {
return fn.apply(object, arguments)
}
}
$ = makeAlias(document, 'getElementById');
>>> $('bn_home')
<body id="bn_home" onload="init();">
Desta forma, você não perca a referência ao objeto original.
Em 2012, há o novo método bind
de ES5 que nos permite fazer isso de uma forma mais extravagante:
>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
Outras dicas
Eu cavou fundo para entender este comportamento particular e eu acho que encontrei uma boa explicação.
Antes de eu entrar para por que você não é capaz de document.getElementById
apelido, vou tentar explicar como funções JavaScript / objetos de trabalho.
Sempre que você chamar uma função JavaScript, o interpretador JavaScript determina um escopo e passa para a função.
Considere seguinte função:
function sum(a, b)
{
return a + b;
}
sum(10, 20); // returns 30;
Esta função é declarada no escopo da janela e quando você chamar-lhe o valor de this
dentro da função soma será o objeto Window
global.
Para a função 'soma', não importa o que o valor de 'isto' é o que não está a usá-lo.
Considere seguinte função:
function Person(birthDate)
{
this.birthDate = birthDate;
this.getAge = function() { return new Date().getFullYear() - this.birthDate.getFullYear(); };
}
var dave = new Person(new Date(1909, 1, 1));
dave.getAge(); //returns 100.
Quando você chama dave.getAge função, o interpretador JavaScript vê que você está chamando a função getAge no objeto dave
, por isso define this
para dave
e chama a função getAge
. getAge()
retornará corretamente 100
.
Você pode saber que em JavaScript você pode especificar o escopo usando o método apply
. Vamos tentar isso.
var dave = new Person(new Date(1909, 1, 1)); //Age 100 in 2009
var bob = new Person(new Date(1809, 1, 1)); //Age 200 in 2009
dave.getAge.apply(bob); //returns 200.
Na linha acima, em vez de deixar JavaScript decidir o escopo, você está passando o escopo manualmente como o objeto bob
. getAge
agora irá retornar 200
mesmo que você 'pensamento' você chamou getAge
no objeto dave
.
O que é o ponto de todos os itens acima? As funções são 'livremente' ligado a objetos o seu JavaScript. Por exemplo. você pode fazer
var dave = new Person(new Date(1909, 1, 1));
var bob = new Person(new Date(1809, 1, 1));
bob.getAge = function() { return -1; };
bob.getAge(); //returns -1
dave.getAge(); //returns 100
Vamos dar o próximo passo.
var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;
dave.getAge(); //returns 100;
ageMethod(); //returns ?????
execução ageMethod
gera um erro! O que aconteceu?
Se você ler os meus pontos acima com cuidado, você gostaria de assinalar que o método dave.getAge
foi chamado com dave
como objeto this
enquanto JavaScript não pôde determinar o 'escopo' para execução ageMethod
. Então ele passou 'Janela' global como 'isto'. Agora, como window
não tem uma propriedade birthDate
, execução ageMethod
falhará.
Como corrigir isso? Simples,
ageMethod.apply(dave); //returns 100.
Será que todo o make sentido acima? Se isso acontecer, então você vai ser capaz de explicar por que você não é capaz de document.getElementById
apelido:
var $ = document.getElementById;
$('someElement');
$
é chamado com window
como this
e se implementação getElementById
está esperando this
ser document
, ele irá falhar.
Mais uma vez para corrigir isso, você pode fazer
$.apply(document, ['someElement']);
Então, por que é que funciona no Internet Explorer?
Eu não sei a implementação interna do getElementById
no IE, mas um comentário em fonte jQuery (implementação do método inArray
) diz que no IE, window == document
. Se for esse o caso, então aliasing document.getElementById
deve funcionar no IE.
Para ilustrar isso ainda mais, eu criei um exemplo elaborado. Ter um olhar para a função Person
abaixo.
function Person(birthDate)
{
var self = this;
this.birthDate = birthDate;
this.getAge = function()
{
//Let's make sure that getAge method was invoked
//with an object which was constructed from our Person function.
if(this.constructor == Person)
return new Date().getFullYear() - this.birthDate.getFullYear();
else
return -1;
};
//Smarter version of getAge function, it will always refer to the object
//it was created with.
this.getAgeSmarter = function()
{
return self.getAge();
};
//Smartest version of getAge function.
//It will try to use the most appropriate scope.
this.getAgeSmartest = function()
{
var scope = this.constructor == Person ? this : self;
return scope.getAge();
};
}
Para a função Person
acima, aqui é como os vários métodos getAge
vai se comportar.
Vamos criar dois objetos usando a função Person
.
var yogi = new Person(new Date(1909, 1,1)); //Age is 100
var anotherYogi = new Person(new Date(1809, 1, 1)); //Age is 200
console.log(yogi.getAge()); //Output: 100.
Em linha reta para a frente, método getAge fica objeto yogi
como this
e 100
saídas.
var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
JavaScript interepreter objeto conjuntos window
como this
e nosso método getAge
voltará -1
.
console.log(ageAlias.apply(yogi)); //Output: 100
Se definir o escopo correto, você pode usar o método ageAlias
.
console.log(ageAlias.apply(anotherYogi)); //Output: 200
Se passar em algum outro objeto pessoa, ele ainda era calcular corretamente.
var ageSmarterAlias = yogi.getAgeSmarter;
console.log(ageSmarterAlias()); //Output: 100
A função ageSmarter
capturado o objeto this
original para agora você não precisa se preocupar com o fornecimento de escopo correto.
console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!
O problema com ageSmarter
é que você nunca pode definir o escopo para algum outro objeto.
var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100
A função ageSmartest
usará o escopo original, se um escopo inválido é fornecido.
console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200
Você ainda será capaz de passar outro objeto Person
para getAgeSmartest
. :)
Esta é uma resposta curta.
O que se segue faz uma cópia de (a referência a) a função. O problema é que agora a função está no objeto window
quando ele foi projetado para viver no objeto document
.
window.myAlias = document.getElementById
As alternativas são
- usar um invólucro (já mencionado por Fabien Ménager)
-
ou você pode usar dois aliases.
window.d = document // A renamed reference to the object window.d.myAlias = window.d.getElementById
Outra resposta curta, apenas para o acondicionamento console.log
/ aliasing e métodos de registro semelhantes. Todos eles esperam estar no contexto console
.
Esta é utilizável quando console.log
embrulho com alguns fallbacks, no caso de você ou seus usuários não ter problemas quando usando um navegador que não (sempre) apoiá-lo . Esta não é uma solução completa para o problema, porém, como ele precisa ser expandida cheques e um fallback -. Sua milhagem pode variar
Exemplo usando avisos
var warn = function(){ console.warn.apply(console, arguments); }
Em seguida, usá-lo como de costume
warn("I need to debug a number and an object", 9999, { "user" : "Joel" });
Se você prefere ver os seus argumentos madeireiras envolto em uma matriz (I fazer, na maioria das vezes), substituto .apply(...)
com .call(...)
.
O trabalho deve com console.log()
, console.debug()
, console.info()
, console.warn()
, console.error()
. Veja também console
em MDN .
Além de outros grandes respostas, não há método jQuery simples $ .proxy .
Você pode apelido como este:
myAlias = $.proxy(document, 'getElementById');
ou
myAlias = $.proxy(document.getElementById, document);
Você realmente não pode "Alias ??pura" uma função em um objeto predefinido. Portanto, o mais próximo de aliasing você pode começar sem embalagem é de ficar dentro do mesmo objeto:
>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">