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