当面试官问你“ 什么是作用域链? ”时,这样回答

363 阅读3分钟

作用域链

先来了解一些名词,也许你现在不知道什么意思,看完不明白请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

我在这里画一个图,应该会好接受一点

原型链.png

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{}

Snipaste_2022-07-04_20-07-03.png

我们看出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]]中,我们把这种链式链接叫做作用域链,作用域链中查找对象是从顶端依次往下查找