Почему свойство arguments.callee.caller устарело в JavaScript? - PullRequest
207 голосов
/ 19 сентября 2008

Почему свойство arguments.callee.caller устарело в JavaScript?

Он был добавлен, а затем объявлен устаревшим в JavaScript, но ECMAScript полностью исключил его. Некоторые браузеры (Mozilla, IE) всегда поддерживали его и не планируют удалять поддержку на карте. Другие (Safari, Opera) приняли его поддержку, но поддержка в старых браузерах ненадежна.

Есть ли веская причина поместить эту ценную функциональность в подвешенное состояние?

(Или, наоборот, есть ли лучший способ получить дескриптор вызывающей функции?)

Ответы [ 5 ]

247 голосов
/ 25 октября 2008

Ранние версии JavaScript не допускали выражений именованных функций, и из-за этого мы не могли создать рекурсивное выражение функции:

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

Чтобы обойти это, было добавлено arguments.callee, чтобы мы могли сделать:

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

Однако на самом деле это было действительно плохое решение, так как (в сочетании с другими аргументами, проблемами вызываемого абонента и вызывающего абонента) делает невозможным встраивание и рекурсию хвоста в общем случае (вы можете добиться этого в отдельных случаях с помощью трассировки и т. Д., Но даже лучший код является субоптимальным из-за проверок, которые в противном случае не были бы необходимы). Другая важная проблема заключается в том, что рекурсивный вызов получит другое значение this, например:

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

Так или иначе, EcmaScript 3 решил эти проблемы, разрешив выражения с именованными функциями, например ::

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

Это имеет множество преимуществ:

  • Функция может быть вызвана как любая другая из вашего кода.

  • Не загрязняет пространство имен.

  • Значение this не изменяется.

  • Это более производительно (доступ к объекту аргументов стоит дорого).

Упс,

Просто понял, что в дополнение ко всему остальному вопрос был о arguments.callee.caller, точнее Function.caller.

В любой момент времени вы можете найти самого глубокого вызывающего абонента из любой функции в стеке, и, как я сказал выше, просмотр стека вызовов имеет один единственный важный эффект: он делает невозможным большое количество оптимизаций или значительно сложнее.

Например. если мы не можем гарантировать, что функция f не будет вызывать неизвестную функцию, то невозможно встроить f. По сути, это означает, что любой сайт вызова, который мог быть тривиально инлинируемым, накапливает большое количество охранников, примите:

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

Если интерпретатор js не может гарантировать, что все предоставленные аргументы являются числами в точке выполнения вызова, ему необходимо либо вставить проверки для всех аргументов перед встроенным кодом, либо он не может встроить функцию.

Теперь в этом конкретном случае умный интерпретатор должен иметь возможность переставить проверки, чтобы они были более оптимальными, и не проверять какие-либо значения, которые не будут использоваться. Однако во многих случаях это просто невозможно, и, следовательно, становится невозможным встроить.

89 голосов
/ 26 августа 2009

arguments.call<b>ee</b>.call<b>er</b> является не устаревшим, хотя и использует свойство Function.call<b>er</b>. (arguments.call<b>ee</b> просто даст вам ссылку на текущую функцию)

  • Function.call<b>er</b>, хотя и нестандартный в соответствии с ECMA3, реализован во всех современных браузерах .
  • arguments.call<b>er</b> является устаревшим в пользу Function.call<b>er</b> и не реализовано в некоторых современных основных браузерах (например, Firefox 3).

Таким образом, ситуация не идеальна, но если вы хотите получить доступ к вызывающей функции в Javascript во всех основных браузерах, вы можете использовать свойство Function.call<b>er</b>, доступ к которому осуществляется непосредственно по ссылке на именованную функцию или из анонимной функция через свойство arguments.call<b>ee</b>.

29 голосов
/ 19 сентября 2008

Лучше использовать именованные функции , чем arguments.callee:

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

лучше

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

Именованная функция будет иметь доступ к своему абоненту через свойство caller :

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

что лучше, чем

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

Устаревание обусловлено действующими принципами проектирования ECMAScript .

15 голосов
/ 21 августа 2009

все еще есть аргумент для ссылки на функцию без необходимости жесткого кодирования ее имени.

0 голосов
/ 22 января 2015

Просто расширение. Значение «это» изменяется во время рекурсии. В следующем (измененном) примере factorial получает объект {foo: true}.

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

factorial, вызываемый впервые, получает объект, но это не так для рекурсивных вызовов.

...