Pourquoi la propriété arguments.callee.caller a-t-elle été déconseillée en JavaScript?

214

Pourquoi la arguments.callee.callerpropriété a- t-elle été dépréciée en JavaScript?

Il a été ajouté puis déconseillé en JavaScript, mais il a été complètement omis par ECMAScript. Certains navigateurs (Mozilla, IE) l'ont toujours pris en charge et n'ont aucun plan sur la carte pour supprimer le support. D'autres (Safari, Opera) l'ont adopté, mais la prise en charge sur les navigateurs plus anciens n'est pas fiable.

Y a-t-il une bonne raison de mettre cette précieuse fonctionnalité dans les limbes?

(Ou alternativement, existe-t-il une meilleure façon de saisir la poignée de la fonction d'appel?)

pcorcoran
la source
2
Il est pris en charge par d'autres navigateurs, car toute fonctionnalité qui obtient un minimum d'utilisation généralisée deviendra un bogue de compatibilité pour d'autres navigateurs. Si un site utilise une fonctionnalité qui n'existe que dans un seul navigateur, le site est cassé dans tous les autres, et les utilisateurs pensent généralement que c'est le navigateur qui est cassé.
olliej
4
(Presque tous les navigateurs l'ont fait à un moment ou à un autre, par exemple, cette fonctionnalité (et JS elle-même) provient de Netscape, XHR est originaire d'IE, Canvas dans Safari, etc. Certains d'entre eux sont utiles et sont récupérés par les autres navigateurs. au fil du temps (js, canvas, xhr sont tous des exemples), certains (.callee) ne le sont pas.
olliej
@olliej Votre commentaire sur le support parce qu'il est utilisé et non pas parce qu'il s'agit d'une norme (ou même en dépit de son obsolète dans la norme) est très vrai! C'est pourquoi j'ai commencé à ignorer principalement les normes chaque fois que je sens qu'elles ne m'aident pas. En tant que développeurs, nous pouvons façonner l'orientation des normes en utilisant ce qui fonctionne et non ce que la spécification dit que nous devrions faire. C'est ainsi que nous sommes arrivés <b>et <i>retournés (oui, ceux-ci ont été dépréciés à un moment donné).
Stijn de Witt

Réponses:

252

Les premières versions de JavaScript ne permettaient pas les expressions de fonction nommées, et à cause de cela, nous ne pouvions pas créer une expression de fonction récursive:

 // 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;
 });

Pour contourner cela, a arguments.calleeété ajouté afin que nous puissions faire:

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

Cependant, c'était en fait une très mauvaise solution car cela (en conjonction avec d'autres arguments, les appels et les problèmes de l'appelant) rend l'inline et la récursivité de la queue impossibles dans le cas général (vous pouvez y parvenir dans certains cas en traçant etc., mais même le meilleur code est sous-optimal en raison de vérifications qui ne seraient pas nécessaires autrement). L'autre problème majeur est que l'appel récursif obtiendra un autrethis valeur , par exemple:

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();

Quoi qu'il en soit, EcmaScript 3 a résolu ces problèmes en autorisant les expressions de fonction nommées, par exemple:

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

Cela présente de nombreux avantages:

  • La fonction peut être appelée comme n'importe quelle autre depuis votre code.

  • Il ne pollue pas l'espace de noms.

  • La valeur de thisne change pas.

  • C'est plus performant (accéder à l' objet arguments coûte cher).

Oups,

Je viens de réaliser qu'en plus de tout le reste, la question portait sur arguments.callee.caller, ou plus précisément Function.caller.

À tout moment, vous pouvez trouver l'appelant le plus profond de n'importe quelle fonction sur la pile, et comme je l'ai dit ci-dessus, regarder la pile d'appels a un seul effet majeur: cela rend un grand nombre d'optimisations impossibles, ou beaucoup plus difficiles.

Par exemple. si nous ne pouvons pas garantir qu'une fonction fn'appellera pas une fonction inconnue, alors il n'est pas possible de s'aligner f. Fondamentalement, cela signifie que tout site d'appel qui peut avoir été trivialement inline accumule un grand nombre de gardes, prenez:

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

Si l'interpréteur js ne peut pas garantir que tous les arguments fournis sont des nombres au moment de l'appel, il doit soit insérer des vérifications pour tous les arguments avant le code intégré, soit il ne peut pas aligner la fonction.

Maintenant, dans ce cas particulier, un interpréteur intelligent devrait être en mesure de réorganiser les contrôles pour être plus optimal et de ne vérifier aucune valeur qui ne serait pas utilisée. Cependant, dans de nombreux cas, ce n'est tout simplement pas possible et il devient donc impossible de s'aligner.

olliej
la source
12
Êtes-vous en train de dire qu'il est obsolète simplement parce qu'il est difficile à optimiser? C'est un peu idiot.
Thomas Eding
11
Non, j'ai énuméré un certain nombre de raisons, en plus de le rendre difficile à optimiser (bien que dans l'histoire générale, les choses difficiles à optimiser aient également une sémantique que les gens ont du mal à suivre)
olliej
17
L' argument this est un peu faux, sa valeur peut être définie par l'appel si c'est important. Habituellement, il n'est pas utilisé (au moins, je n'ai jamais eu de problème avec celui-ci dans les fonctions récursives). L'appel de la fonction par son nom a les mêmes problèmes, thisdonc je pense que cela n'a pas d'importance si l' appelé est bon ou mauvais. En outre, appelé et l' appelant ne sont « déconseillés » en mode strict (ECMAScript ed 5, décembre 2009), mais je suppose que cela n'a pas été connu en 2008 lorsque olliej affiché.
RobG
8
) Je ne vois toujours pas la logique. Dans n'importe quel langage avec des fonctions de première classe, il est clair qu'il est possible de définir un corps de fonction qui peut se référer à lui-même sans avoir à connaître i
Mark Reed
8
RobG l'a souligné, mais je ne pense pas que ce soit clair: la récurrence à l'aide d'une fonction nommée ne préservera que la valeur de thisif thisest la portée globale. Dans tous les autres cas, la valeur de this va changer après le premier appel récursif, donc je pense que les parties de votre réponse en faisant allusion à la préservation de thisne sont pas vraiment valables.
JLRishe
89

arguments.callee.callern'est pas déconseillé, bien qu'il utilise la propriété. ( vous donnera juste une référence à la fonction actuelle)Function.callerarguments.callee

  • Function.caller, bien que non standard selon ECMA3, est implémenté sur tous les principaux navigateurs actuels .
  • arguments.caller est déconseillé en faveur de et n'est pas implémenté dans certains des principaux navigateurs actuels (par exemple Firefox 3).Function.caller

La situation n'est donc pas idéale, mais si vous souhaitez accéder à la fonction appelante en Javascript sur tous les principaux navigateurs, vous pouvez utiliser la propriété, soit accessible directement sur une référence de fonction nommée, soit à partir d'une fonction anonyme via la propriété.Function.callerarguments.callee

James Wheare
la source
5
C'est la meilleure explication de ce qui est et n'est pas obsolète, très utile. Pour un bon exemple de ce que Function.caller ne peut pas faire (obtenir la trace de pile des fonctions récursives), voir developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Juan Mendes
1
Cependant, arguments.calleeest interdit en mode strict. Cela m'a également rendu triste, mais il vaut mieux ne plus l'utiliser.
Gras Double
1
Le lien hypertexte arguments.callee que vous avez vers MDN indique qu'il est supprimé en mode strict. N'est-ce pas la même chose que dépréciée?
styfle
1
Notez qu'il arguments.callee.callerest déconseillé en mode strict ES5: "Une autre fonctionnalité déconseillée était arguments.callee.caller, ou plus précisément Function.caller." ( Source )
thdoan
29

Il vaut mieux utiliser des fonctions nommées que arguments.callee:

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

est mieux que

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

La fonction nommée aura accès à son appelant via la propriété de l' appelant :

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

ce qui est mieux que

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

La dépréciation est due aux principes de conception actuels d'ECMAScript .

Zach
la source
2
Pouvez-vous décrire pourquoi l'utilisation de la fonction nommée est préférable. N'est-il jamais nécessaire d'utiliser callee dans une fonction anonyme?
AnthonyWJones
27
Si vous utilisez callee dans une fonction anonyme, vous disposez d'une fonction qui ne doit pas être anonyme.
Prestaul
3
parfois, le moyen le plus simple de déboguer est avec .caller (). Dans de tels cas, les fonctions nommées n'aideront pas - vous essayez de déterminer quelle fonction effectue l'appel.
SamGoody
6
Définissez mieux. Par exemple, IE6-8 a nommé des fonctions bizarres pendant que arguments.callee fonctionne.
cmc
1
Mis à part les bizarreries IE6-8, il rend également le code étroitement couplé. Si les noms des objets et / ou des fonctions sont codés en dur, alors comme le mentionnent ardsasd et rsk82, il existe des dangers majeurs de refactorisation, qui ne font qu'augmenter à mesure que la taille de la base de code augmente. Les tests unitaires sont une ligne de défense, et je les utilise, mais ce n'est toujours pas une réponse qui me satisfait vraiment personnellement concernant ce problème de codage en dur.
Jasmine Hegman
0

Juste une extension. La valeur de "ceci" change pendant la récursivité. Dans l'exemple suivant (modifié), factorial obtient l'objet {foo: true}.

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

la factorielle appelée la première fois obtient l'objet, mais ce n'est pas le cas pour les appels récursifs.

FERcsI
la source
1
Parce que vous vous trompez. Si cela thisdoit être maintenu, écrivez factorial.call(this, n-1)que j'ai trouvé en écrivant du code récursif, qu'il n'y en a généralement pas this, ou thisfait référence à un nœud dans une arborescence et en fait, c'est bien qu'il change.
Stijn de Witt