Ранние версии 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 не может гарантировать, что все предоставленные аргументы являются числами в точке выполнения вызова, ему необходимо либо вставить проверки для всех аргументов перед встроенным кодом, либо он не может встроить функцию.
Теперь в этом конкретном случае умный интерпретатор должен иметь возможность переставить проверки, чтобы они были более оптимальными, и не проверять какие-либо значения, которые не будут использоваться. Однако во многих случаях это просто невозможно, и, следовательно, становится невозможным встроить.