2009-06-17 11 views
76

Stavo solo leggendo this question 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 finestre di debug che in una pagina Web di test.L'aliasing della funzione JavaScript non sembra funzionare

Firebug:

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

Se l'ho messo 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 >>> s 'inserita per 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 test ottengo la stessa "invocazione illegale".

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

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

+0

in IE8 - potrebbe essere un qualche tipo di funzione magica o qualcosa del genere. –

+0

Ho aggiunto commenti alle risposte errate all'indirizzo http://stackoverflow.com/questions/954417 e li ho indirizzati qui. –

+1

Per i browser che supportano il metodo Function.prototype.bind: window.myAlias ​​= document.getElementById.bind (documento); Cerca nei documenti MDN, ci sarà uno shind bind. –

risposta

33

È necessario associare tale metodo all'oggetto del 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 per l'oggetto 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') 
<body id="bn_home" onload="init();"> 

In questo modo non si perde il riferimento all'oggetto originale.

Nel 2012, c'è la nuova bind metodo dal ES5 che ci permette di fare questo in modo più elaborato:

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

Che è veramente un wrapper perché restituisce una nuova funzione. Penso che la risposta alla domanda di Kev sia che non è possibile per le ragioni che descrivi sopra. Il metodo che descrivi qui è probabilmente l'opzione migliore. – jiggy

+0

Grazie per il tuo commento, non avevo guardato abbastanza da rendermene conto. Quindi, la sintassi nelle risposte sull'altra domanda è completamente falsa? Interessante ... – Kev

+0

In realtà, dovresti inviarlo come risposta ... – Kev

-6

In realtà non può "alias pura" una funzione su un oggetto predefinito . Pertanto, più vicino a aliasing si può ottenere senza avvolgimento viene rimanendo all'interno dello stesso oggetto:

>>> document.s = document.getElementById; 
>>> document.s('myid'); 
<div id="myid"> 
+5

Questo è leggermente errato. Si può 'puro alias' una funzione a condizione che l'uso della funzione di implementazione di 'questo'. Quindi dipende. – SolutionYogi

+0

Scusa, intendevo nel contesto di getElementById. Hai ragione. Ho cambiato leggermente la risposta per riflettere questo ... – Kev

+0

Oh Kev questo è davvero OP lolzzzz –

181

ho scavato in profondità per capire questo particolare comportamento e penso che ho trovato una buona spiegazione.

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

Ogni volta che si richiama una funzione JavaScript, l'interprete JavaScript determina un ambito e lo passa alla funzione.

consideri seguente funzione:

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

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

Questa funzione è dichiarata nel campo di applicazione della finestra e quando si richiama il valore della this all'interno della funzione somma sarà l'Window oggetto globale.

Per la funzione "somma", non importa quale sia il valore di "questo" in quanto non lo utilizza.


Considerate 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 dave.getAge la funzione, l'interprete JavaScript vede che si sta chiamando la funzione getAge sull'oggetto dave, quindi imposta this a dave e chiama il getAge funzione. getAge() restituirà correttamente 100.


Si può sapere che in JavaScript è possibile specificare l'ambito utilizzando 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 sopra, invece di lasciare JavaScript decidere il campo di applicazione, si sta passando l'ambito manualmente come oggetto bob. getAge restituirà ora 200 anche se "pensavamo" di aver chiamato getAge sull'oggetto dave.


Qual è il punto di tutto quanto sopra? Le funzioni sono "vagamente" collegate ai tuoi oggetti JavaScript. Per esempio. si può 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 

Prendiamo il passo successivo.

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

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

ageMethod esecuzione genera un errore! Quello che è successo?

Se leggete con attenzione i miei punti di cui sopra, si dovrebbe notare che dave.getAge metodo è stato chiamato con dave come this oggetto mentre JavaScript non ha potuto determinare il 'ambito' per ageMethod esecuzione. Quindi ha superato "Window" globale come "this". Ora come window non ha una proprietà birthDate, l'esecuzione di ageMethod avrà esito negativo.

Come risolvere il problema? Semplice,

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

Ha fatto tutto quanto sopra ha senso? Se è così, allora si sarà in grado di spiegare il motivo per cui non si è in grado di alias document.getElementById:

var $ = document.getElementById; 

$('someElement'); 

$ viene chiamato con window come this e se getElementById implementazione si aspetta this essere document, fallirà.

Anche in questo caso per risolvere questo problema, si può fare

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

Allora, perché funziona in Internet Explorer?

Non conosco l'implementazione interna di getElementById in IE, ma un commento in jQuery source (implementazione 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 i vari metodi getAge si comporteranno.

Creiamo due oggetti utilizzando 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. 

Straight avanti, metodo getAge ottiene yogi oggetto come this e uscite 100.


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

JavaScript interepreter imposta window oggetto come this e il nostro metodo di getAge tornerà -1.


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

Se si imposta l'ambito corretto, è possibile utilizzare ageAlias metodo.


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

Se passiamo a qualche altro oggetto persona, sarà ancora calcolare l'età in modo corretto.

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

La funzione ageSmarter catturato l'this oggetto originale così ora non c'è bisogno di preoccuparsi di fornire corretta portata.


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

Il problema con ageSmarter è che non si può 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à la portata originale se un ambito valido viene fornita.


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

Sarà ancora in grado di passare un altro oggetto Person a getAgeSmartest. :)

+7

+1 per il motivo per cui IE funziona. Il resto è un po 'fuori tema ma comunque utile in generale.:) – Kev

+42

Ottima risposta. Tuttavia, non vedo come nulla sia fuori tema. –

+0

Risposta molto bella, davvero. Una versione un po 'più breve è scritta [qui] (http://stackoverflow.com/questions/585840/implement-map-in-javascript-that-supports-object-methods-as-mapped-functions/585918#585918). –

3

Questa è una risposta breve.

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

window.myAlias = document.getElementById 

Le alternative sono

  • di utilizzare un involucro (già citato da Fabien Ménager)
  • oppure è possibile utilizzare due alias.

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

Un'altra risposta breve, proprio per il confezionamento/aliasing console.log e simili metodi di registrazione. Tutti si aspettano di trovarsi nel contesto console.

Questo è utilizzabile quando si esegue il wrapping di console.log con alcuni fallback, nel caso in cui l'utente oi suoi utenti si trovassero nei guai quando using a browser that doesn't (always) support it. Tuttavia, non è una soluzione completa a questo problema, in quanto è necessario estendere i controlli e una riserva: il tuo chilometraggio può variare.

esempio con avvertimenti

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

quindi utilizzarlo come al solito

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

Se si preferisce vedere i tuoi argomenti di registrazione avvolti in un array (io lo faccio, la maggior parte del tempo), sostituire .apply(...) con .call(...).

dovrebbe funzionare con console.log(), console.debug(), console.info(), console.warn(), console.error(). Vedi anche console on MDN.

1

In aggiunta alle altre grandi risposte, non c'è metodo semplice jQuery $.proxy.

È possibile alias come questo:

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

O

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

+1 perché è una soluzione fattibile. Ma, a rigor di termini, dietro le quinte '$ .proxy' è anche un wrapper davvero. – pimvdb

Problemi correlati