为什么JavaScript中不赞成arguments.callee.caller属性?
为什么JavaScript中不推荐使用arguments.callee.caller属性?
它是在JavaScript中添加然后弃用的,但ECMAScript完全省略了它。某些浏览器(Mozilla,IE)一直都支持该浏览器,并且在地图上没有删除支持的计划。其他浏览器(Safari,Opera)已对此提供支持,但是对较旧浏览器的支持是不可靠的。
是否有充分的理由将这种有价值的功能置于边缘?
(或者,是否有更好的方法来抓住调用函数的句柄?)
解决方案
最好使用命名函数而不是arguments.callee:
function foo () { ... foo() ... }
胜过
function () { ... arguments.callee() ... }
命名函数将通过caller属性访问其调用者:
function foo () { alert(foo.caller); }
哪个比
function foo () { alert(arguments.callee.caller); }
弃用是由于当前的ECMAScript设计原则所致。
早期版本的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解释器不能保证在调用时提供的所有参数都是数字,则它需要在内联代码之前插入对所有参数的检查,或者不能内联函数。
现在,在这种特殊情况下,智能解释器应该能够将检查重新排列为最佳状态,而不检查任何不会使用的值。但是,在许多情况下这是不可能的,因此无法内联。
仍然有一个参数用于引用该函数,而不必对其功能名称进行硬编码。
尽管arguments.callee.caller确实使用了Function.caller属性,但并未弃用。 (arguments.callee
只会为我们提供对当前函数的引用)
- 尽管根据ECMA3是非标准的,但Function.caller还是在所有当前的主要浏览器中实现的。
- 不赞成使用" arguments.caller",而建议使用" Function.caller",并且在某些当前的主流浏览器(例如Firefox 3)中并未实现。
因此情况并不理想,但是如果要跨所有主要浏览器访问Java中的调用函数,则可以使用Function.caller属性,该属性可以直接在命名函数引用上访问,也可以从匿名函数内部访问通过arguments.callee
属性。