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.

Foi útil?

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">
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top