Early versions of JavaScript did not allow named function expressions, and because of that we could not make a recursive function expression:
// 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;
});
To get around this, arguments.callee
was added so we could do:
[1,2,3,4,5].map(function(n) {
return (!(n>1))? 1 : arguments.callee(n-1)*n;
});
However this was actually a really bad solution as this (in conjunction with other arguments, callee, and caller issues) make inlining and tail recursion impossible in the general case (you can achieve it in select cases through tracing etc, but even the best code is sub optimal due to checks that would not otherwise be necessary). The other major issue is that the recursive call will get a different this
value, for example:
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();
Anyhow, EcmaScript 3 resolved these issues by allowing named function expressions, e.g.:
[1,2,3,4,5].map(function factorial(n) {
return (!(n>1))? 1 : factorial(n-1)*n;
});
This has numerous benefits:
The function can be called like any other from inside your code.
It does not pollute the namespace.
The value of this
does not change.
It's more performant (accessing the arguments object is expensive).
Whoops,
Just realised that in addition to everything else the question was about arguments.callee.caller
, or more specifically Function.caller
.
At any point in time you can find the deepest caller of any function on the stack, and as I said above, looking at the call stack has one single major effect: It makes a large number of optimizations impossible, or much much more difficult.
Eg. if we can't guarantee that a function f
will not call an unknown function, then it is not possible to inline f
. Basically it means that any call site that may have been trivially inlinable accumulates a large number of guards, take:
function f(a, b, c, d, e) { return a ? b * c : d * e; }
If the js interpreter cannot guarantee that all the provided arguments are numbers at the point that the call is made, it needs to either insert checks for all the arguments before the inlined code, or it cannot inline the function.
Now in this particular case a smart interpreter should be able to rearrange the checks to be more optimal and not check any values that would not be used. However in many cases that's just not possible and therefore it becomes impossible to inline.