javascript的作用域链

原创
2015/02/12 09:39
阅读数 118

最近一直想整理几篇好的文章分享给大家,无奈文笔太俗,也是一直懒惰。直到今天才稍微有个样子写出来,因为明天就不上班了。

本文都是自己平时经验积累,难免错误,欢迎指正,请勿拍砖。

开始。

在javascript中,如果说原型链是对象寻找属性的过程,那么作用域链就是在作用域内寻找变量的过程,那么属性和变量又有什么不同吗?

下面举个例子:

function test(){
    var a=1;
}
var obj={b:2}

上面函数test中的a就是变量,使用var 关键字定义,上面的obj对象中的b就是属性,可以知道属性b属于obj这个对象,那么变量a属于哪个对象吗?答案是肯定的,变量a 也是属于某个对象的,接下来我们会介绍这个对象。

介绍这个对象之前,我们必须要了解一下函数的的执行过程,每个函数对应着一个执行上下文,函数执行的过程有两个阶段,一是进入执行上下文,二是执行代码。

进入执行上下文,每个函数都会对应着一个上下文对象(我们姑且这么叫),这个对象有个VO 属性(我们也姑且这么叫),在本函数内定义的变量都是这个VO对象的属性,当然上下文对象和VO对象还有其他的属性内容。

下面通过实例了解一下上下文对象以及VO对象。

function test(i){
    alert(x); // function
    var x = 10;
    alert(x); // 10
    function x() {};
    alert(x); // 10
}
test(10);

很多同学对上面的代码运行结果可能有些疑惑,我们来分析一下上面代码中test函数对应的上下文对象以及VO对象。

第一阶段,进入执行上下文

testExecutionContext = {
            VO: {
                arguments: {
                      0:10
                      length: 1
                },
                x: pointer to function x(),
            },
            scopeChain: { ... },
            this: { ... }
}

第二阶段,执行代码


testExecutionContext = {
            VO: {
                arguments: {
                      0:10
                      length: 1
                },
                x: 10,
            },
            scopeChain: { ... },
            this: { ... }
}

依据这个分析,我们不难得出上面的函数运行结果了。

下面说点题外话

var a=10;
b=12;
上面这个代码有何不同呢?有人可能会说,这是定义了两个全局的变量,其实不然,前者是一个全局变量,后者只是为window对象定义了一个属性。但它们到底何不同呢?这里通过执行上下文就能清晰分析出它们的不同之处
alert(a);//undefined
var a=10;
alert(b);//脚本错误
b=10;
看见了吧,这就是区别,用上下文的方式分析一下。

进入执行上下文,b根本就不在VO对象当中。

VO = {
  a: undefined
};
执行代码阶段,才有这个两个值。
VO = {
  a: 10
  b:10
};

下面回到正题,说作用域链,既然是链,那就不能是一个东西,必须是一串东西。

上例子:

function a(){
	var aa="aa";
	function b(){
		var bb="bb";
		function c(){
			var cc="cc";
			alert(aa);
			alert(bb);
			alert(cc);
		}
		c()
	}	
	b();
}
a();
我们上面的代码,我们在函数c 里面可以使用函数b,函数a里面的变量 bb和aa。其实这就一个链,一层一层的链状结构,内部函数可以使用外部函数的变量。这很简单,下面稍微深入讨论一下原理。


每个函数都有个内部的属性__parent__,这个属性是我们用浏览器访问不到的,如果要访问可以使用Rhino解释器来执行下面的代码。关于这个东西,网上自行搜索吧。

__parent__属性可以获取“上级函数”的VO对象,有些拗口,用上面的例子说吧,函数c,c.__parent__ 就是函数b对应的上下文的VO对象


function a(){
	var aa="aa";
	function b(){
		var bb="bb";
		function c(){
			var cc="cc";
			for(var i in c.__parent__){//这里是函数b的VO对象
				println(i);	//分别是arguments,bb,c
			}
		}
		c()
	}	
	b();
}
a();

上面println(Rhino没有alert)的结果就是函数b对应的VO对象,里面包含参数对象,bb变量,和函数c。


同理,c.__parent__.__parent__ 对应的就是函数a的VO对象
改写上面的代码:
for(var i in c.__parent__.__parent__){//这里就是函数a的VO对象
	println(i);	//分别是arguments,aa,b
}

到这里我们就得出原理,也对作用域链以及作用域寻找变量的过程应该比较清晰了。

下面对上面的a,b,c函数的作用域画个图简单了解一下。

通过上面的图解,我们就可以清晰的发现这确实是一个链,例如:c函数寻找一个变量,会现在c函数的VO对象里面寻找,找不到的话,去c.__parent__也就是b函数的VO对象上寻找,再找不到的话会去c.__parent__.__parent__上也就是a函数的VO对象去找,再找不到的话就回去c.__parent__.parent__.parent__去找也就是全局作用域了,如果还是找不到,没有办法了,直接出错了。是不是很原型链有的一拼。

哈哈,结束。

时间匆匆,难免错误,编辑器也用不好,还望见谅,祝大家新年快乐。

展开阅读全文
打赏
0
6 收藏
分享
加载中
更多评论
打赏
0 评论
6 收藏
0
分享
返回顶部
顶部