Dlaczego właściwość arguments.callee.caller jest przestarzała w JavaScript?

214

Dlaczego arguments.callee.callerwłaściwość jest przestarzała w JavaScript?

Został dodany, a następnie przestarzały w JavaScript, ale został całkowicie pominięty przez ECMAScript. Niektóre przeglądarki (Mozilla, IE) zawsze ją obsługiwały i nie mają żadnych planów na mapie, aby usunąć wsparcie. Inne (Safari, Opera) przyjęły wsparcie dla niego, ale obsługa starszych przeglądarek jest zawodna.

Czy istnieje dobry powód, by tę cenną funkcjonalność wprowadzić w stan zawieszenia?

(Lub na przemian, czy jest lepszy sposób na uchwycenie funkcji wywołania?)

pcorcoran
źródło
2
Jest obsługiwany przez inne przeglądarki, ponieważ każda funkcja, która otrzyma odrobinę powszechnego użytku, stanie się błędem kompatybilności dla innej przeglądarki. Jeśli witryna korzysta z funkcji, która istnieje tylko w jednej przeglądarce, witryna jest uszkodzona we wszystkich innych i zazwyczaj użytkownicy myślą, że to przeglądarka jest zepsuta.
olliej
4
(Prawie wszystkie przeglądarki zrobiły to w tym czy innym czasie, np. Ta funkcja (i sama JS) pochodzi z Netscape, XHR pochodzi z IE, Canvas w Safari itp. Niektóre z nich są przydatne i są odbierane przez inne przeglądarki z biegiem czasu (js, canvas, xhr są przykładami), niektóre (.callee) nie są
olliej
@olliej Twój komentarz na temat wspierania go, ponieważ jest używany, a nie dlatego, że jest standardem (a nawet pomimo tego, że jest przestarzały w standardzie) jest bardzo prawdziwy! Właśnie dlatego zacząłem w większości ignorować standardy, kiedy czuję, że mi nie pomagają. Jako programiści możemy kształtować kierunek standardów, wykorzystując to, co działa, a nie to, co mówi specyfikacja. W ten sposób otrzymaliśmy <b>i <i>wróciliśmy (tak, były one przestarzałe w pewnym momencie).
Stijn de Witt

Odpowiedzi:

252

Wczesne wersje JavaScript nie pozwalały na nazwane wyrażenia funkcyjne, dlatego nie mogliśmy utworzyć rekurencyjnego wyrażenia funkcyjnego:

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

Aby obejść ten problem, arguments.calleedodano, abyśmy mogli:

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

Było to jednak naprawdę złe rozwiązanie, ponieważ (w połączeniu z innymi argumentami, sprawami wywoływanymi i wywołującymi) uniemożliwia wstawianie i rekurencję ogona w ogólnym przypadku (można to osiągnąć w wybranych przypadkach przez śledzenie itp., Ale nawet najlepszy kod jest poniżej optymalnego ze względu na kontrole, które w innym przypadku nie byłyby konieczne). Innym ważnym problemem jest to, że wywołanie rekurencyjne otrzyma inną thiswartość, na przykład:

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

W każdym razie EcmaScript 3 rozwiązał te problemy, umożliwiając nazwane wyrażenia funkcji, np .:

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

Ma to wiele zalet:

  • Funkcja może być wywoływana jak każda inna z twojego kodu.

  • Nie zanieczyszcza przestrzeni nazw.

  • Wartość thisnie zmienia się.

  • Jest bardziej wydajny (dostęp do obiektu argumentów jest kosztowny).

Ups,

Właśnie zdałem sobie sprawę, że oprócz wszystkiego innego pytanie dotyczyło arguments.callee.caller, a dokładniej Function.caller.

W dowolnym momencie na stosie można znaleźć najgłębszą funkcję wywołującą dowolną funkcję, a jak powiedziałem powyżej, przeglądanie stosu wywołań ma jeden istotny efekt: uniemożliwia lub znacznie utrudnia dużą liczbę optymalizacji.

Na przykład. jeśli nie możemy zagwarantować, że funkcja fnie wywoła nieznanej funkcji, nie można wstawić f. Zasadniczo oznacza to, że każda strona wywoławcza, która mogła być trywialnie nieuchronna, gromadzi dużą liczbę strażników, weź:

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

Jeśli interpreter js nie może zagwarantować, że wszystkie podane argumenty są liczbami w momencie wywołania, musi albo wstawić sprawdzanie wszystkich argumentów przed wstawionym kodem, albo nie może wstawić funkcji.

Teraz w tym konkretnym przypadku inteligentny tłumacz powinien być w stanie zmienić kolejność sprawdzania, aby był bardziej optymalny i nie sprawdzał żadnych wartości, które nie byłyby używane. Jednak w wielu przypadkach jest to po prostu niemożliwe i dlatego niemożliwe staje się wprowadzenie.

olliej
źródło
12
Mówisz, że jest deprecjonowany tylko dlatego, że trudno go zoptymalizować? To trochę głupie.
Thomas Eding,
11
Nie, wymieniłem kilka powodów, dodatkowo utrudniających optymalizację (chociaż ogólna historia pokazała, że ​​rzeczy trudne do optymalizacji mają również semantykę, którą ludzie mają trudności z podążaniem)
olliej
17
Ten argument jest nieco fałszywy, jego wartość może być ustawiona przez wywołanie jeśli to ważne. Zwykle nie jest używany (przynajmniej nigdy nie miałem z tym problemu w funkcjach rekurencyjnych). Wywołanie funkcji po imieniu wiąże się z tymi samymi problemami, thiswięc myślę, że nie ma znaczenia, czy odbiorca jest dobry, czy zły. Ponadto odbiorca i osoba dzwoniąca są „przestarzałe” tylko w trybie ścisłym (ECMAscript ed 5, grudzień 2009), ale myślę, że nie było to znane w 2008 roku, kiedy opublikował to olliej.
RobG
8
) Nadal nie widzę logiki. W każdym języku z pierwszorzędnymi funkcjami istnieje wyraźna wartość możliwości definiowania treści funkcji, która może odnosić się do siebie bez konieczności znajomości i
Mark Reed
8
RobG zwrócił na to uwagę, ale nie sądzę, żeby to wszystko było jasne: rekursja przy użyciu nazwanej funkcji zachowa wartość globalnego zasięgu thisif this. We wszystkich pozostałych przypadkach, wartość this będzie zmienić po pierwszym wywołaniu rekurencyjnym, więc myślę, że poszczególne części odpowiedź nawiązując do zachowania thisnie są naprawdę ważne.
JLRishe
89

arguments.callee.callernie jest przestarzałe, chociaż korzysta z właściwości. ( poda tylko odniesienie do bieżącej funkcji)Function.callerarguments.callee

  • Function.caller, chociaż niestandardowy zgodnie z ECMA3, jest implementowany we wszystkich głównych przeglądarkach .
  • arguments.caller jest przestarzałe na rzecz i nie jest zaimplementowane w niektórych głównych przeglądarkach (np. Firefox 3).Function.caller

Sytuacja jest więc mniej niż idealna, ale jeśli chcesz uzyskać dostęp do funkcji wywołującej w Javascripcie we wszystkich głównych przeglądarkach, możesz użyć tej właściwości, albo bezpośrednio w nazwanym odwołaniu do funkcji, albo z funkcji anonimowej za pośrednictwem tej właściwości.Function.callerarguments.callee

James Wheare
źródło
5
To najlepsze wyjaśnienie tego, co jest i nie jest przestarzałe, bardzo przydatne. Dobry przykład tego, czego nie może zrobić Function.caller (pobierz ślad stosu funkcji rekurencyjnych), zobacz developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Juan Mendes
1
Chociaż arguments.calleejest zabroniony w trybie ścisłym. Też mnie zasmuciło, ale lepiej go nie używać.
Gras Double
1
Arguments.callee hiperłącze trzeba MDN mówi, że jest on usuwany w trybie ścisłym. Czy to nie to samo co przestarzałe?
styfle 11.04.17
1
Uwaga: arguments.callee.callerprzestarzała w trybie ścisłym ES5: „Kolejną przestarzałą funkcją była arguments.callee.caller, a dokładniej Function.caller”. ( Źródło )
Thdoan
29

Lepiej jest używać nazwanych funkcji niż arguments.callee:

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

jest lepszy niż

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

Nazwana funkcja będzie miała dostęp do swojego obiektu wywołującego za pośrednictwem właściwości obiektu wywołującego :

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

co jest lepsze niż

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

Wycofanie wynika z bieżącego ECMAScript zasad projektowania .

Zach
źródło
2
Czy możesz opisać, dlaczego użycie nazwanej funkcji jest lepsze. Czy nigdy nie ma potrzeby używania callee w funkcji anonimowej?
AnthonyWJones
27
Jeśli używasz callee w funkcji anonimowej, masz funkcję, która nie powinna być anonimowa.
Prestaul
3
czasami najłatwiejszym sposobem debugowania jest .caller (). W takich przypadkach funkcje nazwane nie pomogą - próbujesz ustalić, która funkcja wykonuje wywołanie.
SamGoody,
6
Zdefiniuj lepiej. Na przykład IE6-8 nazwali dziwactwa funkcji, podczas gdy arguments.callee działa.
cmc
1
Oprócz dziwactw IE6-8, kod również jest ściśle powiązany. Jeśli nazwy obiektów i / lub funkcji są zakodowane na stałe, to jak wspominają ardsasd i rsk82, istnieją poważne niebezpieczeństwa związane z refaktoryzacją, które rosną tylko wraz ze wzrostem podstawowego rozmiaru kodu. Testy jednostkowe są linią obrony i używam ich, ale wciąż nie są odpowiedzią, która naprawdę zadowala mnie osobiście w związku z tym problemem związanym z kodowaniem.
Jasmine Hegman
0

Tylko rozszerzenie. Wartość „this” zmienia się podczas rekurencji. W poniższym (zmodyfikowanym) przykładzie silnia pobiera obiekt {foo: true}.

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

silnia wywoływana za pierwszym razem pobiera obiekt, ale nie dotyczy to wywołań rekurencyjnych.

FERcsI
źródło
1
Ponieważ robisz to źle. Jeślithis trzeba to utrzymać, napisz factorial.call(this, n-1), że znalazłem już podczas pisania kodu rekurencyjnego, że zwykle nie ma this, lub thisodnosi się do jakiegoś węzła w drzewie i tak naprawdę dobrze, że się zmienia.
Stijn de Witt