JavaScript:遍历原型链,调用栈,作用域链
在JavaScript中,有三种常见的链式结构:原型链(Prototype Chain),调用栈(Call Stack),作用域链(Scope Chain).本文并不准备讲这些概念的基础知识,而是要给出如何遍历这三种链结构的方法,从而加深理解.
遍历原型链
在JavaScript中,任何对象都有自己的原型链.原型链是由一系列对象加上最后的null组成的.如果还没掌握相关基础知识,可以看看我在MDN上翻译的继承与原型链一文.遍历函数如下:
function getPrototypeChain(obj) { var protoChain = []; while (obj = Object.getPrototypeOf(obj)) { protoChain.push(obj); } protoChain.push(null); return protoChain; }
尝试执行一下
>getPrototypeChain(new String("")) [String, Object, null] //依次是String.prototype,Object.prototype,null >getPrototypeChain(function(){}) [function Empty() {}, Object, null] //依次是Function.prototype,Object.prototype,null
这个函数是在我以前写的一篇文章JavaScript:我对原型链的理解中给出的.
遍历调用栈
在JavaScript中,调用栈就是一系列的函数,表明当前函数是由哪些上层函数调用的.遍历函数如下:
function getCallStack() { var stack = []; var fun = getCallStack; while (fun = fun.caller) { stack.push(fun) } return stack }
该函数用到了非标准的caller属性,不过主流浏览器都支持它.尝试执行一下:
function a() { b() } function b() { c() } function c() { alert(getCallStack().map(function (fun) { return fun.name //使用了非标准的name属性 })) } a() //弹出c,b,a
b() //弹出c,b
在调试工具中,我们可以直接使用console.trace()来打印出调用栈.在递归调用中,如果调用栈的长度过长,引擎就会抛出异常"too much recursion".到底多长是上限,不同的引擎不同的操作系统环境这个值是不同的.可以使用下面这个函数表达式获取到这个上限值:
> (function(i){try{(function m(){++i&&m()}())}catch(e){return i}})(0) 50761
遍历作用域链
作用域链是由一系列执行上下文(Execution context)中的活动对象(Activation object)加最后的全局对象组成的.活动对象是一个抽象实体(Abstract Entity),它是由引擎内部来管理的,并不能通过JavaScript来访问.看不到,摸不着,所以这些知识就很难理解.
不过在Mozilla的引擎中,有一个魔法属性__parent__可以获取到函数执行时的活动对象.只是在SpiderMonkey中,该属性已经被删除了(Firefox 4开始).不过在Mozilla的另外一个JavaScript引擎Rhino(Java编写)上,还可以使用这个特殊属性.遍历代码如下:
function getScopeChain(fun) { var scopeChain = []; while (fun = fun.__parent__) { scopeChain.push(fun);
} return scopeChain;
}
尝试执行一下:
var a = 0; (function fun1() { var a = 1; (function fun2() { var a = 2; (function fun3() { var a = 3; getScopeChain(function () {}).map(function (obj) { print("-----------------------------") for(var i in obj){ print(i + ":" + (obj[i].name?obj[i].name:obj[i])) } }) })() })() })() ----------------------------- //函数fun3 arguments:[object Arguments] a:3 fun3:fun3 ----------------------------- //函数fun2 arguments:[object Arguments] a:2 fun2:fun2 ----------------------------- //函数fun1 arguments:[object Arguments] a:1 fun1:fun1 ----------------------------- //全局上下文 a:0 getScopeChain:getScopeChain
另外,如果是在Firefox的特权代码中(chrome上下文),还可以使用Debugger API来获取到各种引擎内部隐藏着的数据,Firebug中的以及Firefox自带的调试器,都是用这些API来实现的.