L’aliasing della funzione JavaScript non sembra funzionare

Stavo solo leggendo questa domanda e volevo provare il metodo alias piuttosto che il metodo function-wrapper, ma non riuscivo a farlo funzionare in Firefox 3 o 3.5beta4 o Google Chrome, sia nelle windows di debug che in in una pagina web di prova.

Firebug:

>>> window.myAlias = document.getElementById function() >>> myAlias('item1') >>> window.myAlias('item1') >>> document.getElementById('item1') 

Se lo metto 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 ::  :: line 7" data: no] 

Chrome (con >>> inserito per chiarezza):

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

E nella pagina di test ottengo la stessa “invocazione illegale”.

Sto facendo qualcosa di sbagliato? Qualcun altro può riprodurlo?

Inoltre, stranamente, ho appena provato e funziona in IE8.

Devi associare quel metodo all’object del documento. Guarda:

 >>> $ = document.getElementById getElementById() >>> $('bn_home') [Exception... "Cannot modify properties of a WrappedNative" ... anonymous :: line 72 data: no] >>> $.call(document, 'bn_home')  

Quando si esegue un alias semplice, la funzione viene richiamata sull’object globale, non sull’object del documento. Utilizzare 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')  

In questo modo non perdi il riferimento all’object originale.

Nel 2012, c’è il nuovo metodo di bind di ES5 che ci consente di farlo in un modo più elaborato:

 >>> $ = document.getElementById.bind(document) >>> $('bn_home')  

Ho scavato in profondità per comprendere questo particolare comportamento e penso di aver trovato una buona spiegazione.

Prima di entrare nel motivo per cui non si è in grado di creare alias document.getElementById , proverò a spiegare come funzionano le funzioni / oggetti di JavaScript.

Ogni volta che invochi una funzione JavaScript, l’interprete JavaScript determina un ambito e lo passa alla funzione.

Considera la seguente funzione:

 function sum(a, b) { return a + b; } sum(10, 20); // returns 30; 

Questa funzione è dichiarata nell’ambito della finestra e quando la si invoca il valore di this all’interno della funzione sum sarà l’object Window globale.

Per la funzione “sum”, non importa quale sia il valore di “questo” in quanto non lo utilizza.


Considera 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 si chiama la funzione dave.getAge, l’interprete JavaScript vede che si sta chiamando la funzione getAge sull’object dave , quindi imposta su dave e chiama la funzione getAge . getAge() restituirà correttamente 100 .


Potresti sapere che in JavaScript puoi specificare l’ambito usando il metodo apply . Proviamoci.

 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 precedente, invece di consentire a JavaScript di decidere l’ambito, si passa l’ambito manualmente come object bob . getAge ora restituirà 200 anche se hai ‘pensato’ di aver chiamato getAge sull’object dave .


Qual è il punto di tutto quanto sopra? Le funzioni sono “vagamente” collegate ai tuoi oggetti JavaScript. Ad esempio puoi farlo

 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 ????? 

ageMethod esecuzione ageMethod genera un errore! Quello che è successo?

Se si leggono attentamente i miei punti sopra, si noterà che il metodo dave.getAge è stato chiamato con dave come object mentre JavaScript non è stato in grado di determinare l”ambito’ per ageMethod esecuzione ageMethod . Quindi ha superato “Window” globale come “this”. Ora che la window non ha una proprietà ageMethod , ageMethod esecuzione ageMethod fallirà.

Come risolvere questo? Semplice,

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

Tutto ciò ha senso? Se lo fa, allora sarai in grado di spiegare perché non sei in grado di creare alias document.getElementById :

 var $ = document.getElementById; $('someElement'); 

$ viene chiamato con window quanto this e se getElementById implementazione getElementById si aspetta che this sia document , fallirà.

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 in jQuery source ( inArray metodo inArray ) dice che in IE, window == document . Se questo è il caso, allora l’aliasing document.getElementById dovrebbe funzionare in IE.

Per illustrare ulteriormente, ho creato un esempio elaborato. Dai un’occhiata alla funzione Person qui sotto.

 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 comportano 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. 

Semplice, il metodo getAge ottiene object yogi come this ed emette 100 .


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

JavaScript interepreter imposta l’object window come this e il nostro metodo getAge restituirà -1 .


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

Se impostiamo l’ambito corretto, puoi utilizzare il metodo ageAlias .


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

Se passiamo in qualche altro object, calcolerà comunque l’età correttamente.

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

La funzione ageSmarter catturato l’originale di this object, 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 un altro object.


 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 object Person a getAgeSmartest . 🙂

Questa è una risposta breve.

Quanto segue fa una copia di (un riferimento a) la funzione. Il problema è che ora la funzione si trova sull’object window quando è stato progettato per la vita sull’object document .

 window.myAlias = document.getElementById 

Le alternative sono

  • usare un wrapper (già menzionato da Fabien Ménager)
  • oppure puoi usare due alias.

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

Un’altra risposta breve, solo per il wrapping / aliasing di console.log e metodi di registrazione simili. Tutti si aspettano di essere nel contesto della console .

Questo è utilizzabile quando si console.log wrapping di console.log con alcuni fallback, nel caso in cui tu o i tuoi utenti vi siano problemi durante l’ utilizzo di un browser che non lo supporta (sempre) . Tuttavia, non è una soluzione completa a questo problema, in quanto è necessario estendere i controlli e una riserva: il tuo chilometraggio può variare.

Esempio usando gli avvertimenti

 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 si preferisce vedere gli argomenti di registrazione racchiusi in una matrice (lo faccio, la maggior parte delle volte), sostituire .apply(...) con .call(...) .

Dovrebbe funzionare con console.log() , console.debug() , console.info() , console.warn() , console.error() . Vedi anche console su MDN .

console firebug

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 è ansible “puro alias” una funzione su un object predefinito. Pertanto, il più vicino aliasing che puoi ottenere senza il wrapping è rimanere nello stesso object:

 >>> document.s = document.getElementById; >>> document.s('myid');