L'aliasing della funzione JavaScript non sembra funzionare
-
06-07-2019 - |
Domanda
Stavo solo leggendo questa domanda e volevo provare alias piuttosto che il metodo wrapper funzione, ma non riesco a farlo funzionare in Firefox 3 o 3.5beta4 o Google Chrome, sia nelle finestre di debug che in una pagina Web di prova.
Firebug:
>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">
Se lo inserisco in una pagina Web, la chiamata a myAlias ??mi dà questo errore:
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 (con > > > inserito per maggiore chiarezza):
>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias('item1')
TypeError: Illegal invocation
>>> document.getElementById('item1')
<div id=?"item1">?
E nella pagina di prova ottengo la stessa "quotazione illegale".
Sto facendo qualcosa di sbagliato? Qualcun altro può riprodurlo?
Inoltre, stranamente, ho appena provato e funziona in IE8.
Soluzione
Devi associare quel metodo all'oggetto documento. Guardate:
>>> $ = 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 stai facendo un semplice alias, la funzione viene chiamata sull'oggetto globale, non sull'oggetto documento. Usa una tecnica chiamata chiusure per risolvere questo problema:
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();">
In questo modo non perdi il riferimento all'oggetto originale.
Nel 2012 esiste il nuovo metodo bind
di ES5 che ci consente di farlo in modo più elaborato:
>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
Altri suggerimenti
Ho scavato in profondità per capire questo comportamento particolare e penso di aver trovato una buona spiegazione.
Prima di capire perché non sei in grado di alias document.getElementById
, cercherò di spiegare come funzionano le funzioni / gli oggetti JavaScript.
Ogni volta che invochi una funzione JavaScript, l'interprete JavaScript determina un ambito e lo passa alla funzione.
Valuta la seguente funzione:
function sum(a, b)
{
return a + b;
}
sum(10, 20); // returns 30;
Questa funzione è dichiarata nell'ambito di Window e quando la invochi, il valore di this
all'interno della funzione di somma sarà l'oggetto globale Window
.
Per la funzione 'sum', non importa quale sia il valore di 'this' in quanto non lo sta usando.
Valuta la seguente funzione:
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 chiami la funzione dave.getAge, l'interprete JavaScript vede che stai chiamando la funzione getAge sull'oggetto dave
, quindi imposta this
su dave
e chiama la funzione getAge
. getAge ()
restituirà correttamente 100
.
Potresti sapere che in JavaScript puoi specificare l'ambito usando il metodo apply
. Proviamolo.
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.
Nella riga sopra, invece di lasciare che JavaScript decida l'ambito, si passa l'ambito manualmente come oggetto bob
. getAge
ora restituirà 200
anche se hai "pensato" di aver chiamato getAge
sull'oggetto dave
.
Qual è il punto di tutto quanto sopra? Le funzioni sono "liberamente" associate ai tuoi oggetti JavaScript. Per esempio. puoi fare
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
Facciamo il prossimo passo.
var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;
dave.getAge(); //returns 100;
ageMethod(); //returns ?????
L'esecuzione di ?? ageMethod
genera un errore! Che cosa è successo?
Se leggi attentamente i miei punti precedenti, noterai che il metodo dave.getAge
è stato chiamato con dave
come oggetto questo
mentre JavaScript potrebbe non determina lo 'scope' per l'esecuzione di ageMethod
. Quindi ha passato 'Window' globale come 'this'. Ora poiché window
non ha una proprietà birthDate
, l'esecuzione di ageMethod
fallirà.
Come risolvere questo problema? Semplice,
ageMethod.apply(dave); //returns 100.
Tutto quanto sopra aveva senso? In tal caso, sarai in grado di spiegare perché non sei in grado di alias document.getElementById
:
var $ = document.getElementById;
$('someElement');
$
viene chiamato con finestra
come this
e se l'implementazione di getElementById
prevede this essere
Ancora una volta per risolvere questo problema, puoi farlo
$.apply(document, ['someElement']);
Quindi perché funziona in Internet Explorer?
Non conosco l'implementazione interna di getElementById
in IE, ma un commento nell'origine jQuery (implementazione del metodo inArray
) dice che in IE, window == documento
. In tal caso, aliasing document.getElementById
dovrebbe funzionare in IE.
Per illustrare ulteriormente questo, ho creato un esempio elaborato. Dai un'occhiata alla funzione Person
di seguito.
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();
};
}
Per la funzione Person
sopra, ecco come si comporteranno i vari metodi getAge
.
Creiamo due oggetti usando la funzione 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.
Semplicemente, il metodo getAge ottiene l'oggetto yogi
come this
e genera 100
.
var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
L'interprete JavaScript imposta l'oggetto window
come this
e il nostro metodo getAge
restituirà -1
.
console.log(ageAlias.apply(yogi)); //Output: 100
Se impostiamo l'ambito corretto, puoi usare il metodo ageAlias ??
.
console.log(ageAlias.apply(anotherYogi)); //Output: 200
Se passiamo un oggetto di un'altra persona, calcolerà comunque l'età correttamente.
var ageSmarterAlias = yogi.getAgeSmarter;
console.log(ageSmarterAlias()); //Output: 100
La funzione ageSmarter
ha catturato l'oggetto this
originale, quindi ora non devi preoccuparti di fornire l'ambito corretto.
console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!
Il problema con ageSmarter
è che non puoi mai impostare l'ambito su qualche altro oggetto.
var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100
La funzione ageSmartest
utilizzerà l'ambito originale se viene fornito un ambito non valido.
console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200
Sarai comunque in grado di passare un altro oggetto Person
a getAgeSmartest
. :)
Questa è una risposta breve.
Quanto segue crea una copia di (un riferimento a) la funzione. Il problema è che ora la funzione è sull'oggetto window
quando è stata progettata per vivere sull'oggetto document
.
window.myAlias = document.getElementById
Le alternative sono
- per usare un wrapper (già citato da Fabien M & # 233; nager)
-
oppure puoi usare due alias.
window.d = document // A renamed reference to the object window.d.myAlias = window.d.getElementById
Un'altra breve risposta, solo per il wrapping / aliasing console.log
e metodi di registrazione simili. Si aspettano tutti di trovarsi nel contesto console
.
Questo è utilizzabile quando si racchiudono console.log
con alcuni fallback, nel caso in cui tu o i tuoi utenti avete riscontrato problemi quando . Tuttavia, questa non è una soluzione completa a questo problema, poiché deve essere espansa i controlli e un fallback - il tuo chilometraggio può variare.
Esempio di avvertenze
var warn = function(){ console.warn.apply(console, arguments); }
Quindi usalo come al solito
warn("I need to debug a number and an object", 9999, { "user" : "Joel" });
Se preferisci vedere i tuoi argomenti di registrazione racchiusi in un array (lo faccio, la maggior parte delle volte), sostituisci .apply (...)
con .call (...)
.
Dovrebbe funzionare con console.log ()
, console.debug ()
, console.info ()
, console. warn ()
, console.error ()
. Vedi anche console
su MDN .
Oltre ad altre ottime risposte, esiste un semplice metodo jQuery $ .proxy .
Puoi alias in questo modo:
myAlias = $.proxy(document, 'getElementById');
o
myAlias = $.proxy(document.getElementById, document);
In realtà non puoi " puro alias " una funzione su un oggetto predefinito. Pertanto, il più vicino all'alias che puoi ottenere senza wrapping è rimanere nello stesso oggetto:
>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">