Perché la proprietà argument.callee.caller è stata deprecata in JavaScript?


214

Perché la arguments.callee.callerproprietà è stata deprecata in JavaScript?

È stato aggiunto e quindi deprecato in JavaScript, ma è stato omesso del tutto da ECMAScript. Alcuni browser (Mozilla, IE) lo hanno sempre supportato e non hanno piani sulla mappa per rimuovere il supporto. Altri (Safari, Opera) hanno adottato il supporto per esso, ma il supporto su browser meno recenti non è affidabile.

C'è una buona ragione per mettere questa preziosa funzionalità nel limbo?

(O in alternativa, c'è un modo migliore per afferrare la maniglia della funzione chiamante?)


2
È supportato da altri browser perché qualsiasi funzione che ottiene un minimo di uso diffuso diventerà un bug di compatibilità per altri browser. Se un sito utilizza una funzionalità che esiste solo in un browser, il sito viene interrotto in tutti gli altri e in genere gli utenti pensano che sia il browser a essere danneggiato.
olliej,

4
(Quasi tutti i browser l'hanno fatto una volta o l'altra, ad es. Questa funzione (e JS stesso) proviene da Netscape, XHR è originaria di IE, Canvas in Safari, ecc. Alcuni di questi sono utili e vengono raccolti dagli altri browser nel tempo (js, canvas, xhr sono tutti esempi), alcuni (.callee) non lo sono.
Olliej

@olliej Il tuo commento sul supporto perché è usato e non perché è uno standard (o anche se è deprecato nello standard) è molto vero! Questo è il motivo per cui ho iniziato a ignorare gli standard ogni volta che sento che non mi stanno aiutando. Come sviluppatori, possiamo definire la direzione degli standard usando ciò che funziona e non ciò che le specifiche dicono che dovremmo fare. Questo è quanto abbiamo ottenuto <b>e <i>indietro (sì, quelli sono stati deprecato a un certo punto).
Stijn de Witt,

Risposte:


252

Le prime versioni di JavaScript non consentivano espressioni di funzioni denominate e per questo motivo non siamo riusciti a creare un'espressione di funzione ricorsiva:

 // This snippet will work:
 function factorial(n) {
     return (!(n>1))? 1 : factorial(n-1)*n;
 }
 [1,2,3,4,5].map(factorial);


 // But this snippet will not:
 [1,2,3,4,5].map(function(n) {
     return (!(n>1))? 1 : /* what goes here? */ (n-1)*n;
 });

Per aggirare questo, è arguments.calleestato aggiunto in modo da poter fare:

 [1,2,3,4,5].map(function(n) {
     return (!(n>1))? 1 : arguments.callee(n-1)*n;
 });

Tuttavia, questa è stata in realtà una pessima soluzione in quanto (in combinazione con altri argomenti, chiamate e problemi del chiamante) rende impossibile inline e ricorsione della coda nel caso generale (è possibile ottenerlo in casi selezionati attraverso la traccia ecc., Ma anche il codice migliore è sub ottimale a causa di controlli che non sarebbero altrimenti necessari). L'altro problema principale è che la chiamata ricorsiva avrà un thisvalore diverso , ad esempio:

var global = this;
var sillyFunction = function (recursed) {
    if (!recursed)
        return arguments.callee(true);
    if (this !== global)
        alert("This is: " + this);
    else
        alert("This is the global");
}
sillyFunction();

In ogni caso, EcmaScript 3 ha risolto questi problemi consentendo espressioni di funzioni denominate, ad esempio:

 [1,2,3,4,5].map(function factorial(n) {
     return (!(n>1))? 1 : factorial(n-1)*n;
 });

Ciò ha numerosi vantaggi:

  • La funzione può essere chiamata come qualsiasi altra dall'interno del tuo codice.

  • Non inquina lo spazio dei nomi.

  • Il valore di thisnon cambia.

  • È più performante (l'accesso all'argomento argomenti è costoso).

Ops,

Ho appena realizzato che, oltre a tutto il resto, la domanda riguardava arguments.callee.caller, o più specificamente Function.caller.

In qualsiasi momento puoi trovare il chiamante più profondo di qualsiasi funzione nello stack e, come ho detto sopra, guardare lo stack di chiamate ha un singolo effetto principale: rende impossibile un gran numero di ottimizzazioni, o molto più difficili.

Per esempio. se non possiamo garantire che una funzione fnon chiamerà una funzione sconosciuta, non è possibile inline f. Fondamentalmente significa che qualsiasi sito di chiamata che potrebbe essere stato banalmente inlinabile accumula un gran numero di guardie, prendere:

 function f(a, b, c, d, e) { return a ? b * c : d * e; }

Se l'interprete js non può garantire che tutti gli argomenti forniti siano numeri nel punto in cui viene effettuata la chiamata, deve inserire i controlli per tutti gli argomenti prima del codice inline oppure non può incorporare la funzione.

Ora, in questo caso particolare, un interprete intelligente dovrebbe essere in grado di riorganizzare i controlli per essere più ottimale e non controllare alcun valore che non verrebbe utilizzato. Tuttavia, in molti casi ciò non è possibile e quindi diventa impossibile inline.


12
Stai dicendo che è deprivato solo perché è difficile da ottimizzare? È un po 'sciocco.
Thomas Eding,

11
No, ho elencato una serie di motivi, oltre a renderlo difficile da ottimizzare (anche se nella storia generale ha dimostrato che le cose che sono difficili da ottimizzare hanno anche una semantica che le persone hanno difficoltà a seguire)
olliej

17
Il questo argomento è un po 'spuria, il suo valore può essere impostato dalla chiamata se è importante. Di solito non viene utilizzato (almeno, non ho mai avuto problemi con le funzioni ricorsive). Chiamare la funzione per nome ha gli stessi problemi, thisquindi penso che sia irrilevante se la chiamata è buona o cattiva. Inoltre, callee e chiamante sono solo "deprecato" in modalità rigorosa (ECMAscript Ed 5, dic 2009), ma credo che non si sapeva nel 2008, quando olliej postato.
RobG,

8
) Ancora non vedo la logica. In qualsiasi linguaggio con funzioni di prima classe, c'è un chiaro valore nel poter definire un corpo di funzione che può riferirsi a se stesso senza dover conoscere i
Mark Reed,

8
RobG lo ha sottolineato, ma non credo che fosse tutto così chiaro: il ricorrere utilizzando una funzione con nome preserverà il valore di thisif se thisè l'ambito globale. In tutti gli altri casi, il valore di this si cambia dopo la prima chiamata ricorsiva, quindi penso che le parti della vostra risposta che alludono alla conservazione del thisnon sono realmente validi.
JLRishe

89

arguments.callee.callernon è obsoleto, sebbene utilizzi la proprietà. ( ti darà solo un riferimento alla funzione corrente)Function.callerarguments.callee

  • Function.caller, sebbene non standard secondo ECMA3, è implementato su tutti i principali browser attuali .
  • arguments.caller è deprecato a favore e non è implementato in alcuni dei principali browser attuali (ad esempio Firefox 3).Function.caller

Quindi la situazione è tutt'altro che ideale, ma se si desidera accedere alla funzione di chiamata in Javascript su tutti i principali browser, è possibile utilizzare la proprietà, a cui si accede direttamente su un riferimento di funzione denominato o all'interno di una funzione anonima tramite la proprietà.Function.callerarguments.callee


5
Questa è la migliore spiegazione di ciò che è e non è deprecato, molto utile. Per un buon esempio di ciò che Function.caller non può fare (ottenere la traccia dello stack di funzioni ricorsive), vedere developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Juan Mendes

1
Tuttavia, arguments.calleeè vietato in modalità rigorosa. Mi ha anche reso triste, ma è meglio non usarlo più.
Gras Double,

1
Il collegamento ipertestuale argomenti.callee a MDN dice che è stato rimosso in modalità rigorosa. Non è lo stesso di deprecato?
Styfle

1
Si noti che arguments.callee.callerè deprecato in modalità rigorosa ES5: "Un'altra caratteristica che è stata deprecata era argomenti.callee.caller, o più specificamente Function.caller." ( Fonte )
thdoan

29

È meglio usare le funzioni nominate di argomenti.callee:

 function foo () {
     ... foo() ...
 }

è meglio di

 function () {
     ... arguments.callee() ...
 }

La funzione nominata avrà accesso al suo chiamante tramite la proprietà caller :

 function foo () {
     alert(foo.caller);
 }

che è meglio di

 function foo () {
     alert(arguments.callee.caller);
 }

L'ammortamento è dovuto agli attuali principi di progettazione ECMAScript .


2
Puoi descrivere perché è meglio usare la funzione nominata. Non c'è mai la necessità di usare la chiamata in una funzione anonima?
AnthonyWJones,

27
Se stai usando la chiamata in una funzione anonima, allora hai una funzione che non dovrebbe essere anonima.
Prestaul,

3
a volte il modo più semplice per eseguire il debug è con .caller (). In tali casi, le funzioni denominate non sono di aiuto: stai cercando di capire quale funzione sta eseguendo la chiamata.
SamGoody,

6
Definisci meglio. Ad esempio, IE6-8 ha delle stranezze di funzioni mentre argomenti.callee funziona.
cmc

1
A parte le stranezze di IE6-8, rende anche il codice strettamente accoppiato. Se i nomi degli oggetti e / o delle funzioni sono hardcoded, allora come menzionano ardsasd e rsk82, ci sono grandi pericoli di refactoring, che aumentano solo all'aumentare della dimensione della base di codice. I test unitari sono una linea di difesa e li uso, ma non sono ancora una risposta che mi sazia davvero personalmente riguardo a questo problema di hardcoding.
Jasmine Hegman,

0

Solo un'estensione. Il valore di "questo" cambia durante la ricorsione. Nel seguente esempio (modificato), factorial ottiene l'oggetto {foo: true}.

[1,2,3,4,5].map(function factorial(n) {
  console.log(this);
  return (!(n>1))? 1 : factorial(n-1)*n;
},     {foo:true}     );

fattoriale chiamato la prima volta ottiene l'oggetto, ma questo non è vero per le chiamate ricorsive.


1
Perché stai sbagliando. Se ha thisbisogno di essere mantenuto, scrivi factorial.call(this, n-1)che ho effettivamente trovato nella scrittura del codice ricorsivo, che di solito non c'è this, o si thisriferisce a qualche nodo in un albero ed effettivamente è buono che cambi.
Stijn de Witt,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.