Effective JavaScript Item 29 避免使用非规范的Stack Inspection属性
来源:程序员人生 发布时间:2014-10-03 08:00:00 阅读次数:1770次
本系列作为Effective JavaScript的读书笔记。
由于历史原因,很多JavaScript执行环境中都提供了某些方式来查看函数调用栈。在一些环境中,arguments对象(关于该对象可以查看Item
22,23,24)上有两个额外的属性:
arguments.callee -
它引用了正在被调用的函数
arguments.caller -
它引用了调用当前函数的函数
关于arguments.callee的使用,可以参考下面的代码:
var factorial = (function(n) {
return (n <= 1) ? 1 : (n * arguments.callee(n - 1));
});
可见,在递归函数中,可以使用callee来得到当前正在被调用的函数。
但是,使用函数声明的方式也可以很方便的实现函数的递归调用,并且这种方式更加清晰:
function factorial(n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
而对于arguments.caller,它提供的功能就更加强大了,能保存了调用当前函数的函数的一个引用。因为它有安全隐患,所以很多JavaScript运行环境都将这个属性移除了。同时,有部分运行环境在函数对象上提供了一个caller属性来达到和arguments.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();
trace; // [f1, f2]
但是当一个函数在调用栈中出现不止一次时,就会发生问题了,比如下面的代码会产生一个死循环:
function f(n) {
return n === 0 ? getCallStack() : f(n - 1);
}
var trace = f(1); // infinite loop
原因在于,当发生递归调用时,函数自身会被赋值给它的caller属性。因此getCallStack中的for循环的终止条件f永远不会为false:
for (var f = getCallStack.caller; f; f = f.caller) {
stack.push(f);
}
正因为这种不稳定性和由此带来的安全性问题,在ES5的strict
mode中,使用caller或者callee属性都是被禁止的:
function f() {
"use strict";
return f.caller;
}
f(); // error: caller may not be accessed on strict functions
总结:
- 避免使用arguments对象上的callee和caller属性。
- 避免使用function对象上的caller属性。
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠