Je lisais juste cette question et je voulais essayer la méthode alias plutôt que la méthode wrapper de fonction, 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 dans une page Web de test.
Pyromane:
>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">
Si je le mets 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 >>> 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, je reçois le même "appel illégal".
Est-ce que je fais quelque chose de mal? Quelqu'un d'autre peut-il reproduire cela?
Aussi, curieusement, je viens d'essayer et cela fonctionne dans IE8.
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 alias simple, la fonction est appelée sur l'objet global, pas 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, il y a la nouvelle méthode bind
d'ES5 qui nous permet de le faire de manière plus sophistiquée:
>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
J'ai creusé profondément pour comprendre ce comportement particulier et je pense avoir trouvé une bonne explication.
Avant de comprendre pourquoi vous ne pouvez pas alias document.getElementById
, Je vais essayer d'expliquer comment fonctionnent les fonctions/objets JavaScript.
Chaque fois que vous appelez une fonction JavaScript, l'interpréteur JavaScript détermine une étendue et la transmet à la fonction.
Considérez la fonction suivante:
function sum(a, b)
{
return a + b;
}
sum(10, 20); // returns 30;
Cette fonction est déclarée dans la portée Window et lorsque vous l'invoquez, la valeur de this
à l'intérieur de la fonction sum sera l'objet global Window
.
Pour la fonction "somme", peu importe la valeur de "ceci" car elle ne l'utilise pas.
Considérez 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 la fonction dave.getAge, l'interpréteur JavaScript voit que vous appelez la fonction getAge sur l'objet dave
, il définit donc this
sur dave
et appelle le getAge
. getAge()
renverra correctement 100
.
Vous savez peut-être qu'en JavaScript, vous pouvez spécifier la portée à l'aide de la méthode apply
. Essayons ça.
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, vous passez la portée manuellement en tant qu'objet bob
. getAge
renverra désormais 200
Même si vous "pensiez" avoir appelé getAge
sur l'objet dave
.
Quel est l'intérêt de tout ce qui précède? Les fonctions sont attachées de manière "lâche" à vos objets JavaScript. Par exemple. tu peux 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
Passons à l'étape suivante.
var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;
dave.getAge(); //returns 100;
ageMethod(); //returns ?????
L'exécution de ageMethod
génère une erreur! Qu'est-il arrivé?
Si vous lisez attentivement mes points ci-dessus, vous remarquerez que la méthode dave.getAge
A été appelée avec dave
comme objet this
alors que JavaScript n'a pas pu déterminer la "portée" de ageMethod
exécution. Il a donc passé la "fenêtre" globale comme "ceci". Maintenant que window
n'a pas de propriété birthDate
, l'exécution de ageMethod
échouera.
Comment régler ceci? Facile,
ageMethod.apply(dave); //returns 100.
Est-ce que tout ce qui précède avait du sens? Si c'est le cas, vous pourrez expliquer pourquoi vous ne pouvez pas alias document.getElementById
:
var $ = document.getElementById;
$('someElement');
$
Est appelé avec window
comme this
et si l'implémentation de getElementById
prévoit que this
sera document
, il le fera échouer.
Encore une fois pour résoudre ce problème, vous pouvez le faire
$.apply(document, ['someElement']);
Alors pourquoi ça marche dans Internet Explorer?
Je ne connais pas l'implémentation interne de getElementById
dans IE, mais un commentaire dans la source jQuery (implémentation de la méthode inArray
) dit que dans IE, window == document
. Si c'est le cas, l'alias document.getElementById
Devrait fonctionner dans IE.
Pour illustrer cela davantage, j'ai créé un exemple élaboré. Jetez un œil à la fonction Person
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 la fonction Person
ci-dessus, voici comment les différentes méthodes getAge
se comporteront.
Créons deux objets en utilisant la fonction 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.
Directement, la méthode getAge obtient l'objet yogi
sous la forme this
et génère 100
.
var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
L'interpréteur JavaScript définit l'objet window
comme this
et notre méthode getAge
renverra -1
.
console.log(ageAlias.apply(yogi)); //Output: 100
Si nous définissons l'étendue correcte, vous pouvez utiliser la méthode ageAlias
.
console.log(ageAlias.apply(anotherYogi)); //Output: 200
Si nous passons dans un autre objet personne, il calculera toujours correctement l'âge.
var ageSmarterAlias = yogi.getAgeSmarter;
console.log(ageSmarterAlias()); //Output: 100
La fonction ageSmarter
a capturé l'objet this
d'origine, vous n'avez donc plus à vous soucier de fournir une portée correcte.
console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!
Le problème avec ageSmarter
est que vous ne pouvez jamais définir la portée d'un autre objet.
var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100
La fonction ageSmartest
utilisera la portée d'origine si une portée non valide est fournie.
console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200
Vous pourrez toujours passer un autre objet Person
à getAgeSmartest
. :)
Ceci est une réponse courte.
Ce qui suit fait une copie de (une référence à) la fonction. Le problème est que la fonction se trouve maintenant sur l'objet window
lorsqu'elle a été conçue pour vivre sur l'objet document
.
window.myAlias = document.getElementById
Les alternatives sont
ou vous pouvez utiliser deux alias.
window.d = document // A renamed reference to the object
window.d.myAlias = window.d.getElementById
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);