如果将一个局部变量看做是自定义实现的对象的属性的话,那么可以换个角度来解读变量作用域。每一段代码都有一个与之相关联的作用域链。下面举例来理解作用域链(一个页面的作用域链可以看成一棵树)。
var global_var = "global"; // 第1步
function test(x){
var y = 0;
return x+y;
} //第2步
test();//第3步
test();//第4步
function checkscope(){
var scope = "local scope";
function nested(){ //嵌套函数的定义,我的理解是在外部函数调用时才定义。每次调用都会定义一次,没调用时就没定义。
var scope = "nested scope";
return scope;
}
return nested();//第7步
}//第5步
checkscope();//第6步,返回的结果是 nested scope
下面逐步分析作用域链的变化。
0. 初始化创建作用域链的第一个结点,即树T的根节点,名为window的结点。
第1步,.定义一个全局变量后(var global_var),树T在window结点下添加一个孩子结点global_var。
第2步,定义函数test后,树T在window结点下添加一个孩子结点test。
第3步, 调用test函数时,会创建一个新对象(该对象不可见,内部实现)来存储它的局部变量。假设创建的对象名称为_test_obj_1。那么树T需要在window结点下添加孩子结点_test_obj_1。并且_test_obj_1结点有2个孩子x和y。(当test执行完成后,垃圾自动回收功能会自动释放_test_obj_1对象,不一定是立即释放)
第4步,再次调用test函数,会创建一个新对象。对象名称假设是_test_obj_2。那么树T需要在window结点下添加孩子结点_test_obj_2。并且_test_obj_2结点有2个孩子x和y。(当test执行完成后,垃圾自动回收功能会自动释放_test_obj_2对象,不一定是立即释放)
第5步,定义了一个嵌套函数。树T在window结点下添加一个孩子结点checkscope。
第6步,调用嵌套函数的外部函数checkscope时。会创建一个新对象,假设名称是_checkscope_obj_1。window结点下添加孩子_checkscope_obj_1。此时会定义函数nested。_checkscope_obj_1结点下添加2个孩子nested和scope。
第7步,调用嵌套函数nested时。会创建一个新对象,假设名称是_nested_obj_1。树T在_checkscope_obj_1结点下添加一个孩子结点_nested_obj_1和scope。
每次调用外部函数checkscope时,内部函数都回重新定义一遍。每次调用外部函数时作用域链是不同的。如上面test函数调用了2次生成_test_obj_1和_test_obj_2。
ps:_nested_obj_1对象应该还有个nested结点和scope结点。
scope结点被nested定义的scope给覆盖了。
如果不nested不定义scope,其实也可以直接访问scope,其值是"local scope"。
作用域链如下图: