标签:
许多js环境都提供检查调用栈的功能。调用栈是指当前正在执行的活动函数链。在某些旧的宿主环境中,每个arguments对象含有两个额外的属性:arguments.callee和arguments.caller。前者指向使用该arguments对象被调用的函数。后者指向调用该arguments对象被调用的函数的函数。许多环境支持arguments.callee,但它除了允许匿名函数递归地引用自身之外,没有更多的用途了。(高3中认为使用arguments.callee可以解除函数体内的代码和函数名之间的耦合,看来也不是完全没有用的)
下面是一个简单的图示,可以容易了解arguments的callee,caller,及函数的caller
var factorial=(function(n){ return (n<=1)?1:(n*arguments.callee(n-1)); })
但这个也是特别的有用,可以使用函数名来引用函数自身
function factorial(n){ return (n<=1)?1:(n*factorial(n-1)); }
arguments.caller属性更为强大。它指向的是使用该arguments对象调用函数的函数。出于安全考虑,大多数环境已经移除了此特性,因此用它时要检测一下才行。许多JS环境也提供了一个相似的函数对象属性--非标准但普遍适应的caller属性。它指向函数最近的调用者。
function revealCaller(){ return revealCaller.caller; } function start(){ return revealCaller(); } start()===start;//true;
可以利用该属性获取一个提供当前调用栈快照的数据结构。构建一个栈跟踪:
function getCallStack(){ var stack=[]; for(var f=getCallStack.caller;f;f=f.caller){ stack.push(f); } return stack; }
使用示例
function f1(){ return getCallStack(); } function f2(){ return f1(); } var trace=f2();//[f1(), f2()]
脆弱性,当某个函数在调用栈中出现不止一次,那么栈检查逻辑将会陷入循环。
function f(n){ return n===0?getCallStack():f(n-1); } var trace=f(1);//
问题出在哪?由于函数f递归地调用其自身,因此其caller属性会自动更新,指回到函数f。所以,函数getCallStack会陷入无限地查找函数f的循环之中。即使我们试图检测该循环,但在函数f调用其自身之前也没有关于哪个函数调用了它的信息。因为其他调用栈的信息已经丢失了。
这些栈检查属性都是非标准的,在移植性或适用性上很受限制。在ES5的严格模式的函数中,它们是被禁止使用的。试图获取严格函数或arguments对象的caller或callee属性都将报错。
function f(){ ‘use strict‘; return f.caller; } f();//Uncaught TypeError: ‘caller‘ and ‘arguments‘ are restricted function properties and cannot be accessed in this context.(…)
最好的策略是完全避免栈检查。如果检查栈的理由完全是为了测试,那么更为可靠的方式是使用交互式的调试器。
避免使用非标准的arguments.caller和arguments.callee属性,它们不具备良好的移植性
避免使用非标准的函数对象caller属性,因为在包含全部栈信息方面,它是不可靠的
附录:这个没附录,放水一篇,因为这节实在没什么可写的。
[Effective JavaScript 笔记]第29条:避免使用非标准的栈检查属性
标签:
原文地址:http://www.cnblogs.com/wengxuesong/p/5570821.html