你的位置:首页 > Java教程

[Java教程][Effective JavaScript 笔记]第29条:避免使用非标准的栈检查属性


许多js环境都提供检查调用栈的功能。调用栈是指当前正在执行的活动函数链。在某些旧的宿主环境中,每个arguments对象含有两个额外的属性:arguments.callee和arguments.caller。前者指向使用该arguments对象被调用的函数。后者指向调用该arguments对象被调用的函数的函数。许多环境支持arguments.callee,但它除了允许匿名函数递归地引用自身之外,没有更多的用途了。(高3中认为使用arguments.callee可以解除函数体内的代码和函数名之间的耦合,看来也不是完全没有用的)

图示

下面是一个简单的图示,可以容易了解arguments的callee,caller,及函数的caller
1465378571303

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属性,因为在包含全部栈信息方面,它是不可靠的

附录:这个没附录,放水一篇,因为这节实在没什么可写的。