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

214

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

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

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

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

pcorcoran
источник
2
Он поддерживается другими браузерами, потому что любая функция, которая получает широкое распространение, станет ошибкой совместимости для другого браузера. Если сайт использует функцию, которая существует только в одном браузере, сайт ломается во всех остальных, и обычно пользователи думают, что это браузер, который сломан.
olliej
4
(Почти все браузеры делали это в то или иное время, например, эта функция (и сама JS) происходит из Netscape, XHR, созданный в IE, Canvas в Safari и т. Д. Некоторые из них полезны и используются другими браузерами. Со временем (js, canvas, xhr - все примеры), некоторые (.callee) - нет.
olliej
@olliej Ваш комментарий о том, что он поддерживается, потому что он используется, а не потому, что он является стандартом (или даже несмотря на то, что он устарел в стандарте), очень верный! Вот почему я начал игнорировать стандарты, когда чувствую, что они мне не помогают. Мы, как разработчики, можем определять направление стандартов, используя то, что работает, а не то, что в спецификации сказано, что мы должны делать. Это, как мы получили <b>и <i>обратно (да, это были устаревшими в одной точке).
Стейн де Витт

Ответы:

252

Ранние версии 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 стоит дорого).

Упс,

Просто понял, что помимо всего прочего речь шла о arguments.callee.caller, а точнее Function.caller.

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

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

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

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

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

olliej
источник
12
Вы говорите, что он ограничен только потому, что его трудно оптимизировать? Это глупо.
Томас Эдинг
11
Нет, я перечислил ряд причин, помимо того, что это затрудняет оптимизацию (хотя в целом история показала, что вещи, которые трудно оптимизировать, также имеют семантику, за которой людям трудно следовать)
olliej
17
Этот аргумент немного поддельный, его значение может быть установлено вызовом , если это важно. Обычно он не используется (по крайней мере, у меня никогда не было проблем с рекурсивными функциями). Вызов функции по имени имеет те же проблемы, thisпоэтому я думаю, что это не имеет значения в отношении того, хорош ли вызываемый объект или нет. Кроме того, вызываемый и вызывающий абоненты "не рекомендуется" только в строгом режиме (ECMAscript ed 5, декабрь 2009 г.), но я думаю, что это не было известно в 2008 г., когда был опубликован olliej.
RobG
8
Я до сих пор не вижу логики. В любом языке с первоклассными функциями есть четкая ценность в том, чтобы иметь возможность определять тело функции, которое может ссылаться на себя, не зная i
Марк Рид
8
RobG указал на это, но я не думаю, что все было так ясно: повторение с использованием именованной функции сохранит только значение thisif, thisявляющееся глобальной областью действия. Во всех остальных случаях значение this будет меняться после первого рекурсивного вызова, так что я думаю , что части вашего ответа намекая на сохранение thisне совсем корректные.
JLRishe
89

arguments.callee.callerне считается устаревшим, хотя он использует собственность. ( просто даст вам ссылку на текущую функцию)Function.callerarguments.callee

  • Function.callerхотя и нестандартный согласно ECMA3, он реализован во всех современных основных браузерах .
  • arguments.caller является устаревшим в пользу , а не реализована в некоторых текущих основных браузерах (например , Firefox 3).Function.caller

Таким образом, ситуация не идеальна, но если вы хотите получить доступ к вызывающей функции в Javascript во всех основных браузерах, вы можете использовать свойство, доступ к которому осуществляется непосредственно по ссылке на именованную функцию или из анонимной функции через свойство.Function.callerarguments.callee

Джеймс Уир
источник
5
Это лучшее объяснение того, что является и не является устаревшим, очень полезно. Хороший пример того, что Function.caller не может сделать (получить трассировку стека рекурсивных функций), см. На developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Хуан Мендес,
1
Хотя arguments.calleeв строгом режиме это запрещено. Это меня тоже огорчило, но лучше больше этим не пользоваться.
Двойной
1
Arguments.callee гиперссылка вы должны MDN говорит , что он удаляется в строгом режиме. Разве это не то же самое, что устарел?
Styfle
1
Обратите внимание, что arguments.callee.callerне рекомендуется в строгом режиме ES5: «Еще одна особенность, которая устарела, была arguments.callee.caller или, более конкретно, Function.caller». ( Источник )
thoan
29

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

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

лучше, чем

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

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

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

что лучше чем

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

Устаревание связано с текущими принципами проектирования ECMAScript .

Zach
источник
2
Можете ли вы описать, почему использование названной функции лучше. Нет ли необходимости использовать вызываемого абонента в анонимной функции?
AnthonyWJones
27
Если вы используете вызываемый в анонимной функции, то у вас есть функция, которая не должна быть анонимной.
Prestaul
3
иногда самый простой способ отладки - с помощью .caller (). В таких случаях именованные функции не помогут - вы пытаетесь определить, какая функция выполняет вызов.
SamGoody
6
Определись лучше. Например, IE6-8 назвал функцию quirks, а arguments.callee работает.
CMC
1
Помимо особенностей IE6-8, он также делает код тесно связанным. Если имена объектов и / или функций жестко запрограммированы, то, как упоминают ardsasd и rsk82, существуют серьезные опасности при рефакторинге, которые увеличиваются только при увеличении размера базы кода. Модульные тесты - это линия защиты, и я использую их, но они все еще не являются ответом, который действительно насытит меня лично в связи с этой проблемой жесткого кода.
Жасмин Хегман
0

Просто продолжение. Значение «это» изменяется во время рекурсии. В следующем (измененном) примере 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}     );

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

FERcsI
источник
1
Потому что ты делаешь это неправильно. Если thisнеобходимо поддерживать, напишите, factorial.call(this, n-1)что я действительно нашел в написании рекурсивного кода, которого обычно нет this, или thisссылается на некоторый узел в дереве, и на самом деле это хорошо, что он изменяется.
Стейн де Витт