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