理解JavaScript作用域链
理解JavaScript作用域至关重要,它是理解闭包的前提条件。
先通过理解execution context来了解javascript在执行代码之前和之后做了什么工作,可以说理解了理解execution context之后再来理解javascript的作用域就是非常简单的事了。
1.作用域链产生的时机
作用域链:由一组对象按照规定的顺序组成。
全局作用域链:在执行全局代码之前,创建全局execution context过程中产生了作用域链,此作用域链(列表)仅仅包含了一个全局对象。
函数作用域链:在调用函数时,执行函数体代码之前,为函数创建execution context过程中产生了作用域链,它按照一定的顺序包含之前所有execution context的活动对象,并将当前执行函数的活动放置在作用域的最前端。
如果把作用域链当前一个对象,此作用域链并不能直接用javascript来访问操作。作用域链由execution context中内部一个[[scope]]属性来引用。由javascript内部(javascipt解释器)来使用。
2.作用域链销毁
当作用域链所在的execution context被销毁(回收)时,作用域链同时也被销毁(回收)。闭包有所不同,这里暂不作讨论。
3变量内部赋值(系统内部) -- 变量实例化过程
这里所说的变量我们都暂时理解为使用var定义局部变量(把全局变量理解为全局局部变量),把函数名也当前变量看待(包括函数表达式和函数声明)。
在execution context创建过程当中,有一个步骤就是“变量实例化”。即为所有当前execution context定义局部变量,在活动对象上创建一个相应的命名属性。并将这些属性赋值为undefined。
注意:函数声明与一般变量(包括函数表达式)有所不同。 与函数声明相应的命名属性在变量实例化过程中被直接赋值为根据函数声明而创建的函数对象。
4.变量实际赋值 -- 代码执行过程
在代码执行过程中,与局部变量相应的命名属性(活动对象的属性)将由相应的赋值表达式求值时进行真正赋值。
5.在函数中创建的全局变量
在函数创建的非var声明的变量,在函数体代码执行到此变量所在语句时,该变量将会转换成全局变量。
6.实例讲解
6.1我创建的变量无效
if (!(“a” in window)){
var a = 1;
}
alert(a); // 1 ? wrong.
</script>
如果不理解execution context和作用域链,一般人都认为答案应该是1。真正的答案为undefined。
分析:
我们可以把执行javascript代码过程定义为两个步骤:
a.创建execution context,如图6.1-a;
图6.1-a
根据javascript没有块级(if, for等中定义的变量)作用域,这里在if中定义a依然是属于一个全局变量。在创建execution context时被为之创建相应的同名属性a,在变量实例化时将a赋值为undefined。
b.执行execution context中的代码。
在执行”a” in window这个if判断之前,我们看到“a”已经是window的属性了,且赋值为undefined,所以”a” in window为true,这时并没有进入if语句内部,var a=1就变成了一条永远都执行不到的语句了。alert(a)就会为undefined。
6.2我声明的变量怎么变成了函数(var foo;与var foo=undefined;不等价)
function foo() {
// code...
}
var foo; // 这里这句有用吗?理解:仅仅只是一个声明
alert( typeof foo); // undefined ? Wrong.
</script>
分析:
真正答案是“function”。我们依然使用6.1题所示的分析方式。
a.创建execution context,如图6.2-a;
在创建execution context过程中,由于foo是一个函数声明,所以直接被赋值为一个函数对象。var foo再声明foo变量时,发现foo已经在window对象中,所以不作任何处理。
b.执行execution context中的代码。
var foo在这里仅仅只是一个声明,没有赋值的作用。所以并不会改变foo的值。foo依然引用着函数对象。所以在typeof foo返回就是function。
假设:将var foo换成var foo=undefined,foo的值就会被改变为undefined。typeof foo返回undefined。
所以推荐在声明任何变量时尽量进行初始化,不然可能会得到意想不到的结果。