82 votes

L'aliasing de la fonction JavaScript ne semble pas fonctionner

Je viens de lire cette question et je voulais tenter l'alias de la méthode plutôt que la fonction-wrapper méthode, mais je n'arrivais pas à le faire fonctionner dans Firefox 3 ou 3.5beta4, ou Google Chrome, à la fois dans leurs fenêtres de débogage et de test de la page web.

Firebug:

>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">

Si je l'ai mis dans une page web, l'appel à myAlias me donne cette erreur:

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 (avec >>>'s inséré pour plus de clarté):

>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias('item1')
TypeError: Illegal invocation
>>> document.getElementById('item1')
<div id=?"item1">?

Et dans la page de test, j'ai le même "Illégal invocation".

Suis-je en train de faire quelque chose de mal? Une autre personne peut reproduire?

Aussi, curieusement, j'ai juste essayé et il fonctionne dans IE8.

184voto

SolutionYogi Points 16697

J'ai creusé profondément pour comprendre ce comportement et je pense avoir trouvé une bonne explication.

Avant d'entrer dans les raisons pour lesquelles vous n'êtes pas en mesure de faire un alias document.getElementById, je vais essayer d'expliquer comment des fonctions JavaScript/objets de travail.

Chaque fois que vous appeler une fonction JavaScript, l'interpréteur JavaScript détermine la portée et la passe à la fonction.

Envisager la fonction suivante:

function sum(a, b)
{
    return a + b;
}

sum(10, 20); // returns 30;

Cette fonction est déclarée dans la Fenêtre de la portée et quand vous l'appelez la valeur de this à l'intérieur de la fonction somme sera le global Window objet.

Pour la "somme" de la fonction, il n'a pas d'importance ce que la valeur de 'ce' est qu'il n'est pas à l'utiliser.


Envisager la fonction suivante:

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.

Lorsque vous appelez dave.getAge fonction, l'interpréteur JavaScript voit que vous appelez getAge fonction de l' dave de l'objet, de sorte qu'il définit this de dave et appelle l' getAge fonction. getAge() sera bien de retour 100.


Vous savez peut-être qu'en JavaScript, vous pouvez spécifier la portée à l'aide de l' apply méthode. Nous allons essayer que.

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.

Dans la ligne ci-dessus, au lieu de laisser JavaScript décider de la portée, de la vous êtes de passage à la portée manuellement comme l' bob objet. getAge sera de retour 200 même si vous "pensée" vous avez appelé, getAge sur le dave objet.


Quel est le point de tout ce qui précède? Les fonctions sont "lâche" attaché à vos objets en JavaScript. E. g. vous pouvez le faire

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

Nous allons passer à l'étape suivante.

var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;

dave.getAge(); //returns 100;
ageMethod(); //returns ?????

ageMethod d'exécution renvoie une erreur! Ce qui s'est passé?

Si vous avez lu mes points ci-dessus attentivement, vous notez que dave.getAge méthode a été appelée avec dave comme this objet alors que JavaScript n'a pas pu déterminer la "portée" pour ageMethod d'exécution. Donc, il est passé au mondial "Fenêtre", comme "il". Maintenant que window n'ont pas d' birthDate de la propriété, ageMethod l'exécution échoue.

Comment résoudre ce problème? Simple,

ageMethod.apply(dave); //returns 100.

Avez-tous les ci-dessus, le sens? Si c'est le cas, vous serez en mesure d'expliquer pourquoi vous n'êtes pas en mesure de alias document.getElementById:

var $ = document.getElementById;

$('someElement'); 

$ est appelée avec window comme this et si getElementById mise en œuvre attendait this être document, ce sera un échec.

De nouveau pour résoudre ce problème, vous pouvez le faire

$.apply(document, ['someElement']);

Alors pourquoi cela fonctionne dans Internet Explorer?

Je ne connais pas le fonctionnement interne d' getElementById dans IE, mais un commentaire en jQuery source (inArray méthode de la mise en œuvre) dit que, dans IE, window == document. Si c'est le cas, alors aliasing document.getElementById devrait fonctionner sous IE.

Pour illustrer cela, j'ai créé un complexe exemple. Jetez un oeil à l' Person fonction ci-dessous.

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();
    };

}

Pour l' Person de la fonction ci-dessus, voici comment les différents getAge méthodes va se comporter.

Nous allons créer deux objets à l'aide de Person fonction.

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.

En ligne droite, getAge méthode obtient l' yogi objet en tant que this et sorties 100.


var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1

JavaScript interepreter ensembles window objet en tant que this et notre getAge méthode retournera -1.


console.log(ageAlias.apply(yogi)); //Output: 100

Si nous mettons de la bonne portée, vous pouvez utiliser ageAlias méthode.


console.log(ageAlias.apply(anotherYogi)); //Output: 200

Si nous passons à une autre personne, objet, il continuera de calculer l'âge correctement.

var ageSmarterAlias = yogi.getAgeSmarter;    
console.log(ageSmarterAlias()); //Output: 100

L' ageSmarter fonction capturé l'original this objet alors maintenant, vous n'avez pas à vous inquiéter au sujet de la fourniture de domaine approprié.


console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!

Le problème avec ageSmarter , c'est que vous ne pouvez jamais définir le champ d'application à un autre objet.


var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100

L' ageSmartest fonction de l'utilisation de l'étendue d'origine si une étendue non valide est fourni.


console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200

Vous serez toujours en mesure de passer à un autre Person objet d' getAgeSmartest. :)

39voto

Maciej Łebkowski Points 2869

Vous devez lier cette méthode à l'objet document. Regardez:

 >>> $ = 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();">
 

Lorsque vous créez un simple alias, la fonction est appelée sur l'objet global et non sur l'objet document. Utilisez une technique appelée fermetures pour résoudre ce problème:

 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();">
 

De cette façon, vous ne perdez pas la référence à l'objet d'origine.

En 2012, la nouvelle méthode bind d'ES5 nous permet de le faire d'une manière plus sophistiquée:

 >>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
 

3voto

George Bailey Points 13735

C'est une réponse courte.

Ce qui suit fait une copie de (une référence à) la fonction. Le problème est que maintenant la fonction est sur l'objet window alors qu'il était conçu pour vivre sur l'objet document .

 window.myAlias = document.getElementById
 

Les alternatives sont

  • utiliser un wrapper (déjà mentionné par Fabien Ménager)
  • ou vous pouvez utiliser deux alias.

     window.d = document // A renamed reference to the object
    window.d.myAlias = window.d.getElementById
     

2voto

Joel Purra Points 7366

Une autre réponse courte, juste pour l'emballage/aliasing console.log et similaires méthodes de journalisation. Tous, ils s'attendent à être dans l' console contexte.

C'est utilisable lorsque l'habillage console.log avec quelques réserves, dans le cas où vous ou vos utilisateurs ont eu des problemes lors de l' utilisation d'un navigateur qui n'est pas (toujours) le supporte. Ce n'est pas une solution complète à ce problème, car il doit être élargie de contrôles et d'un repli de - votre kilométrage peut varier.

Exemple d'utilisation de mises en garde

var warn = function(){ console.warn.apply(console, arguments); }

Ensuite l'utiliser comme d'habitude

warn("I need to debug a number and an object", 9999, { "user" : "Joel" });

Si vous préférez voir votre journalisation des arguments enveloppé dans un tableau (je ne, la plupart du temps), substitut .apply(...) avec .call(...).

Devrait fonctionner avec console.log(), console.debug(), console.info(), console.warn(), console.error(). Voir aussi console sur MDN.

1voto

Sangdol Points 3374

En plus d'autres bonnes réponses, il existe une méthode jQuery simple, $ .proxy .

Vous pouvez alias comme ceci:

 myAlias = $.proxy(document, 'getElementById');
 

Ou

 myAlias = $.proxy(document.getElementById, document);
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X