225

Why was the arguments.callee.caller property deprecated in JavaScript?

It was added and then deprecated in JavaScript, but it was omitted altogether by ECMAScript. Some browser (Mozilla, IE) have always supported it and don't have any plans on the map to remove support. Others (Safari, Opera) have adopted support for it, but support on older browsers is unreliable.

Is there a good reason to put this valuable functionality in limbo?

(Or alternately, is there a better way to grab a handle on the calling function?)

Kara
  • 6,115
  • 16
  • 50
  • 57
pcorcoran
  • 7,894
  • 6
  • 28
  • 26
  • 2
    It's supported by other browsers because any feature that gets a modicum of widespread use will become a compatibility bug for other browser. If a site uses a feature that only exists in one browser, the site is broken in all others, and typically users think that it's the browser that is broken. – olliej Oct 25 '08 at 02:04
  • 4
    (Almost all browsers have done this at one time or another, eg. this feature (and JS itself) comes from Netscape, XHR originated in IE, Canvas in Safari, etc. Some of these are useful and are picked up by the other browsers over time (js, canvas, xhr are all examples), some (.callee) are not. – olliej Oct 25 '08 at 02:08
  • @olliej Your comment about supporting it because it is used and not because it is a standard (or even despite it being deprecated in the standard) is very true! This is why I started mostly ignoring the standards whenever I feel they are not helping me. We as developers can shape the direction of standards by using what works and not what the spec says we should do. This is how we got `` and `` back (yes, those were deprecated at one point). – Stijn de Witt Apr 17 '17 at 20:22

4 Answers4

264

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.

Pacerier
  • 86,231
  • 106
  • 366
  • 634
olliej
  • 35,755
  • 9
  • 58
  • 55
  • 12
    Are you saying it is depricated just because it's hard to optimize? That's kinda silly. – Thomas Eding Aug 17 '10 at 23:57
  • 11
    No, i listed a number of reasons, in addition to it making it hard to optimise (although in general history has shown that things that are hard to optimise also have semantics that people have difficulty following) – olliej Aug 27 '10 at 09:23
  • 19
    The *this* argument is a bit spurious, its value can be set by the call if it's important. Usually it's not used (at least, I've never had an issue with it in recursive functions). Calling the function by name has the same issues with `this` so I think it's irrelevant in regard to whether *callee* is good or bad. Also, *callee* and *caller* are only "deprecated" in strict mode (ECMAscript ed 5, Dec 2009), but I guess that wasn't known in 2008 when olliej posted. – RobG May 27 '11 at 03:24
  • 9
    )I still don't see the logic. In any language with first-class functions, there's clear value in being able to define a function body that can refer to itself without having to know i – Mark Reed Oct 20 '11 at 19:41
  • arguments.callee.caller gives you the function that called you, not the function you're in, which isn't overly useful in real code, and has the effect of making inlining, tail calls, etc unsound. – olliej Oct 21 '11 at 16:13
  • 1
    Note that you don't need named functions (nor `arguments.callee`) in order to make recursive functions, as you can use the [Y combinator](http://en.wikipedia.org/wiki/Fixed-point_combinator#Example_in_JavaScript). – Suzanne Soy Aug 17 '12 at 07:20
  • 1
    Deprecation because of a different `this` is silly as well. That's what we have `Function.prototype.call` and `Function.prototype.apply` for. – Swivel Jun 26 '13 at 22:40
  • 8
    RobG pointed this out, but I don't think it was all that clear: recursing using a named function will only preserve the value of `this` if `this` is the global scope. In all other cases, the value of `this` _will_ change after the first recursive call, so I think the parts of your answer alluding to the preservation of `this` are not really valid. – JLRishe Jan 22 '14 at 13:31
  • 2
    About the introduction of arguments.callee, for the sake of humanity why not stop with anonymous inline functions all together and just pass a function reference to functions like `map`. It's not because JS let's you write code like that, that it's a good idea. It obscures code structure, and if you don't name them, they will show up as 'anonymous function' in debuggers and profilers. I wish the JS developers would learn some basic programming concepts to stop writing unreadable, undebuggable, unprofilable, poorly designed and thus generally buggy code. –  Jan 30 '15 at 17:05
  • 1
    @nus If you are using anonymous functions by passing them as event handlers it doesn't obscure anything. Furthermore it makes clear and ensures that the function is only called when the event is fired. – fishbone Mar 18 '15 at 11:49
  • @olliej. whats the `!` for in `!(n>1))? 1` – Abhi Jul 05 '16 at 11:49
  • @nus. whats the `!` for in `!(n>1))? 1` – Abhi Jul 05 '16 at 11:50
  • @iLiveInAPineappleUnderTheSea The `!` means NOT as a boolean operator in most programming languages, checkout javascript operators. The whole statement `return (!(n>1))? 1 : factorial(n-1)*n;` translates to: If n is not bigger than 1 return 1 else return the result of factorial... In any case it would be more readable by removing the extranous parenthesis and the negation: `return n<=1 ? 1 : factorial( n-1 ) * n` –  Jul 05 '16 at 13:03
  • Actually, before ES3, there was **no function expression**. instead, there were `Function` constructor. so the specification said: > Anonymous functions are created dynamically by using the built-in Function object as a constructor, which is referred to as an instantiating Function > A property is created with name callee and property attributes { DontEnum }. The initial value of this property is the function object being executed. This allows anonymous functions to be recursive. – ENvironmentSet Jan 30 '19 at 22:10
  • Umm... Why is this answer almost word-for-word what's on this page? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee – Andrew Oct 18 '19 at 20:10
  • 2
    @Andrew that page now seems to credit this page as its source... Good to know that a highly debated stack-overflow answer is now next in essentially standard reference. – Nathan Chappell Mar 18 '21 at 07:41
  • 1
    @NathanChappell Haha, nice. That was not the intention of my asking. :) Maybe the author is somehow affiliated or part of the devs involved, or maybe when they wrote that article they said, "Yep! I agree with that guy." – Andrew Mar 19 '21 at 15:21
90

arguments.callee.caller is not deprecated, though it does make use of the Function.caller property. (arguments.callee will just give you a reference to the current function)

  • Function.caller, though non-standard according to ECMA3, is implemented across all current major browsers.
  • arguments.caller is deprecated in favour of Function.caller, and isn't implemented in some current major browsers (e.g. Firefox 3).

So the situation is less than ideal, but if you want to access the calling function in Javascript across all major browsers, you can use the Function.caller property, either accessed directly on a named function reference, or from within an anonymous function via the arguments.callee property.

James Wheare
  • 4,650
  • 2
  • 26
  • 22
  • 5
    This is the best explanation of what is and isn't deprecated, very useful. For a good example of what Function.caller can't do (get the stack trace of recursive functions), see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/caller – Ruan Mendes Nov 09 '11 at 19:18
  • 2
    Though, `arguments.callee` is forbidden in strict mode. It made me sad too, but it's better to no longer use it. – Gras Double Apr 14 '15 at 23:25
  • 1
    The [arguments.callee](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee) hyperlink you have to MDN says that it is removed in strict mode. Is that not the same as deprecated? – styfle Apr 11 '17 at 20:19
  • 1
    Note that `arguments.callee.caller` is deprecated in ES5 strict mode: "Another feature that was deprecated was arguments.callee.caller, or more specifically Function.caller." ([Source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee)) – thdoan May 16 '18 at 10:42
  • Function.caller is not allowed in strict mode: Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them – Wolfgang Blessen Jun 25 '21 at 07:25
  • 1
    `Function.caller` is also deprecated now – Ooker Jul 28 '23 at 07:33
29

It is better to use named functions than arguments.callee:

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

is better than

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

The named function will have access to its caller through the caller property:

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

which is better than

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

The deprecation is due to current ECMAScript design principles.

Zach
  • 24,496
  • 9
  • 43
  • 50
  • 2
    Can you describe why using the named function is better. Is there never a need to use callee in an anonymous function? – AnthonyWJones Sep 19 '08 at 18:49
  • 28
    If you are using callee in an anonymous function, then you have a function that should not be anonymous. – Prestaul Sep 19 '08 at 19:42
  • 3
    sometimes the easiest way to debug is with .caller(). In such cases named functions won't help - you are trying to work out which function is doing the calling. – SamGoody Dec 13 '10 at 11:02
  • 6
    Define better. For example, IE6-8 [have named function quirks](http://kangax.github.com/nfe/#jscript-bugs) while arguments.callee works. – cmc Jan 21 '12 at 02:27
  • 1
    Aside from the IE6-8 quirks, it also makes code tightly coupled. If the names to objects and/or functions is hardcoded, then as ardsasd and rsk82 mention there are major refactoring dangers, which only increase as the code base size increases. Unit tests are a line of defense, and I use them, but they're still not an answer that really satiates me personally regarding this hardcoding issue. – Jasmine Hegman May 01 '13 at 18:43
  • I'd really prefer some consistent way to refer to the function object from the function body...without relying on the name. DRY. – vaughan Aug 25 '23 at 13:06
0

It still works in js strict mode / type="module" by using new Function. but detected malware by kapersky anti virus

<script type="module">
let fn = new Function(`e`,`
   new Function('console.log(arguments.callee.caller)')()
`)
fn(5)
</script>
B25Dec
  • 2,301
  • 5
  • 31
  • 54
perona chan
  • 101
  • 1
  • 8