在上一篇文章中我写了关于闭包的产生、闭包的好处、闭包造成的问题、闭包问题的解决办法,但是里面涉及了一些关于作用域和作用域链的问题,下面是关于介绍。
作用域([[scope]]):变量或函数的有效访问范围
这是比较官方的解释,下面用一个具体的小实例解释一下,什么叫可访问的范围:
var global = 99;
function a(){
var first = 88;
function b (){
var second = 77;
console.log(first);//88
console.log(second);//77
}
console.log(global);//99
console.log(first);//88
}
console.log(global);//99
console.log(first);//undefined
console.log(second);//undefined
上面最后三个console.log输出的结果其实不是这样的,因为会报错,因为你在a函数和b函数外边去输出first和second,first和second是在a函数和b函数里面声明的,所以你在外面是访问不到他们的,所以会抛出一个ReferenceError,表示你将一个值分配给一个不存在的变量,即你在这个范围你无法访问first和second。类似的如果你在a函数里边访问second也是会报错的。
穿插一个小知识:执行期上下文(AO)在函数执行的前一刻,会创建一个称为执行期上下文的内部对象,一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,而当函数执行完毕,对应的执行期上下文被销毁
那么作用域和执行期上下文有什么联系勒,因为作用域[[scope]]里面存贮的是执行期上下文的集合,作用域里面的执行期上下文的集合呈现链式链接,所以我们把这种链式链接叫做作用域链
那么作用域和作用域链和前篇的闭包有什么关系呢?
之前讲过,闭包产生的两种常见方式,一是函数里面利用return 将另外一个函数返回(保存)到外部,在外部执行,会生成一个闭包,二是在某一个对象或者函数里面将一个函数或者对象赋给一个全局变量,由这个全局变量在外部执行。那么为什么在外部执行时,不会像刚刚那种情况一样,访问first和second时报错呢,用具体例子来说明:
var global = 100;
function a(){
var c = 0;
function b(){
console.log(c++);
}
return b;
}
var temp = a();
temp();//0
temp();//1
temp();//2
为什么我们在外面还是可以访问到a勒,而且每调用一次temp函数,a就会加一,是因为在我们调用a()的前一刻就会形成一个执行期上下文,这个执行期上下文里面就包含了函数b,只不过b此时还没有执行,我们把a函数的执行期上下文写一下:
a的AO
{
c : undefined;
b : function () {...}
}
当a函数执行之后,AO里面的c就会被赋值为0,再调用temp函数,那么在执行前一刻也要形成一个执行期上下文,所以再来写一下temp的执行期上下文:
b的AO
{
啥也没有,因为在b函数里面没有变量声明和函数声明
}
但是当你的temp函数执行完了之后,他要输出c啊,他自己的AO里面没有,所以就会顺着所谓的作用域链向上找,它的上面是什么呢,当然是a的AO,a的AO里面有c,所以可以直接拿过来用,直接输出0,但是你c++了,所以改变的是a的AO里面的c,所以下一次再调用temp函数时,就会输出1...
下面用图展示一下,作用域链的真实情况,方便理解:
左边就是一个作用域链的相似表示,外层函数的 AO永远在上面,所以最里层的函数的访问权限最大,所有的函数里面的变量都可以访问到,外层函数的访问权限一次减小。
如果你要在a函数里面输出global,那就会沿着作用域链向上找,直到到达GO的部分。