我们先来了解几个概念:
执行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。
<pre>解析1:执行期上下文指的就是函数执行前一刻所产生的AO对象</pre>
<pre>解析2:函数执行环境就是指变量提升函数提升得到的那些AO对象属性</pre>
<pre>解析3:
function test() { }
函数多次调用,产生不同的AO对象:
test(); ---->AO{}
test(); ---->AO{}
函数执行完毕之后对应的AO对象销毁。
</pre>
[[scope]]:每个js函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供js引擎存取,[[scope]]就是其中一个。
function test() { }
我们可以访问的函数属性(如:test.length/test.prototype);
我们不能访问但着实存在的函数属性(如:test.[[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]]最顶端去查找变量。
再深入剖析看看,检查自己是否理解清楚了:
首先,a作用域中的顶端AO与b作用域的第二个AO是同一个AO的引用么?还是两个不同的AO?
答案:是同一个AO的引用,下面代码来验证一下
function a() {
function b() {
var b=234;
aa=0;
}
var aa=123;
b( );
console.log(aa);//输出0,代表变量aa在语句b()执行完之后,值被改变了。
}
var glob = 100;
a();
GO{
glob:100,
a:~~~~~
}
AO(a){
aa:0,
b:~~~~~~
}
再来,在上面的例子中,第7行b函数执行完之后,概念上是执行期上下文被销毁,而实际函数a和函数b的作用域变化应该是什么样的呢?
答案:
- b函数执行完之后,自己的执行期上下文AO被干掉(销毁),即b.[[scope]]回到b被定义的状态。
- 往下进行到第9行,a函数执行完后,b函数作用域b.[[scope]]直接被销毁;同时,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执行了才会产生c的定义哈!!
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)。