作用域链
先来了解一些名词,也许你现在不知道什么意思,看完不明白请cue我,那肯定是我的原因
执行期上下文
当函数在执行的时候,会创建一个称为“执行期上下文”的内部对象(AO对象)(Activation Object),一个执行期上下文定义了一个函数执行的环境,函数每次执行时对应的执行上下文是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,当函数执行完毕,它所产生的执行期上下文会被销毁
我们需要记住:函数执行的时候会创建一个内部对象(AO对象)并且执行完毕会被销毁
查找变量
从作用域的顶端依次往下查找。当找到了第一个就会停止查找,遮蔽效应
[[scope]]
函数的作用域,是不可访问的,其中存储了运行期上下文的集合
作用域链
[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链 可能有人已经看的云里雾里了,这里用简短的代码来慢慢解释
//函数是对象,是对象就可以具备属性
function test(){}
//test.name
//test.prototype
//test.[[scope]] 作用域属性 隐式属性
看完以上例子,你对也许有了一些了解,那么我们接着往下看
function test(){
var a = 1
}
//函数在执行之前会预编译,创建一个AO对象(全局代码执行会创建GO global Object)
AO:{
a:undefined
}
//函数执行之后,那么这个函数的AO对象就会被回收
test()——>{}
那么接下来我们就在作用域链上理解了
function test1(){
function test2(){
var a = 1
}
var b = 2
test2()
}
var global = 100
test()
剖析代码
function test1(){}
test1的定义给我们带来了test1.[[scope]]
var global = 100
全局代码的执行会创建 GO{} 此时test1.[[scope]]——>0:GO{}
此时test1可以访问GO{}
test()
test1的执行会创建了AO{} 此时test1.[[scope]]——>0:AO{} 1:GO{}
后创建的会放在前面,在作用域里面的排列方式,这个是规则,现在在test1的作用域里可以访问到AO和GO
我在这里画一个图,应该会好接受一点
test1的定义,带来了test1的作用域,在作用域里,排在顶端的有0:AO,里面有test2:function(){},有b:2;排在后面的有1:GO,里面有test1:function(){},global:100,这是test1可以访问的内容
函数test1执行,test2开始执行
test2()
test2的定义,带来了test2.[[scope]],创建了自己的bAO:{},并位于作用域链的顶端 此时test2.[[scope]]——>0:bAO{} 1:aAO{} 2:GO{}
我们看出test2的作用域链为:test2.[[scope]]——>0:bAO{} 1:aAO{} 2:GO{} -----test1的作用域链为test1.[[scope]]——>0:AO{} 1:GO{}
问:为什么普遍情况test1不能访问test2的作用域?
答:因为函数的AO对象会在函数的执行完毕回收。test2在调用结束之后,test2的AO回收了,但是此时test1未必执行完毕,如果test1想要访问test2的数据,极大可能会出现BUG,所以JS这门语言一般不允许外层函数访问内层数据
总结,面试官问到时该怎么回答?
仅代表个人主观回答 : 函数在执行的时候会创建一个执行上下文的内部对象(AO对象),它定义了一个函数的执行环境,该对象在函数执行完毕会被回收,多次调用函数可以多次创建,将这些函数上下文以链式存储在函数的作用域[[SCOPE]]中,我们把这种链式链接叫做作用域链,作用域链中查找对象是从顶端依次往下查找