函数对任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且可以在任何地方、任何时候调用执行。在javascript里,函数即对象,程序可以随意操控它们。函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量,函数里边能访问函数外边的变量,但函数外边不能访问函数里边的变量它给javascript带来了非常强劲的编程能力。
关于js中函数的作用域链,要想理解函数的作用域链,首先我们得引出几个重要的概念。
执行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被 “销毁”。
函数定义的时候会产生一个全局的作用对象我们称之为GO对象。
函数调用的前一刻产生的执行期上下文我们称之为AO对象,
作用域[[scope]]:每个js函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供js引擎存取,[[scope]]就是其中一个。[[scope]]其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。
结合着例子来理解一下:
function a() {
function b() {
var b=234;
}
}
var glob = 100;
a();
a函数被定义时发生如下过程:
先不要细琢磨上面GO里放置的各个属性。上面a.[[scope]]还没有构成一个链,只有GO对象的存在,下面继续。
a函数被执行时,发生如下过程:
a函数执行前一刻所产生的AO对象放到了a函数作用域a.[[scope]]的顶端;现在a.[[scope]]上已构成一个链.
-
查找变量:从作用域链的顶端依次向下查找(再补充:在哪个函数里查找变量,就去哪个函数的作用域顶端去查找),最标准的说法。
再继续研究刚刚的例子:
function a() { function b() { var b=234; } } var glob = 100; a();
b函数被创建时,发生如下过程:
也就是说b函数刚刚出生时所在的环境是a执行的结果,直接给b函数的出生创造好了环境。
b函数被执行时,发生如下过程:
b函数执行前一刻产生的AO对象放置在b.[[scope]]的最顶端。现在透彻地理解一下,在函数b中去访问变量时,是在b函数的作用域[[scope]]最顶端去查找变量。
需要注意的一点 第7行b函数执行完之后,自己的执行期上下文AO被干掉(销毁),即b.[[scope]]回到b被定义的状态。(即此时b函数的作用域链就是a的作用域链
- 往下进行到第9行,a函数执行完后,b函数作用域b.[[scope]]直接被销毁,因为b函数的作用域链创建于a的执行期上下文AO之上;同时,a函数的执行期上下文AO被干掉(销毁),a.[[scope]]回到被定义的状态,a函数等待下一次被调用执行。
我们在看一下下面函数执行的作用域链:
function a() {
function b(){
function c() {
}
c();
}
b();
}
a();
具体过程如下
a 定义: a.[[scope]] --> 0:GO
a 执行: a.[[scope]] --> 0:AO(a)
1:GO
b 定义: b.[[scope]] --> 0:AO(a)
1:GO
b 执行: b.[[scope]] --> 0:AO(b)
1:AO(a)
2:GO
c 定义: c.[[scope]] --> 0:AO(b)
1:AO(a)
2:GO
c 执行: c.[[scope]] --> 0:AO(c)
1:AO(b)
2:AO(a)
3:GO
现在再来看这句话,函数里边能访问函数外边的变量,但函数外边不能访问函数里边的变量;从上边的过程来看,在b中访问c中的局部变量,是不可能的,因为b.[[scope]](作用域链)中不存在函数c的执行期上下文AO©。c可以访问b和a中的局部变量,因为c.[[scope]](作用域链)中包括函数b和函数a的执行期上下文 AO(b),AO(a)。