image-20210406175259388

所谓的作用域可以简单理解成一个房子,在全局中,我们有个全局的作用域,在这个房子里所有的东西都可以使用,此时如果有个函数function a() {}, 那么在这个全局的房子中就会生成一个命名为a的一个小房间,在这个小房间中,我们可以拿到外面的大房子的东西,但是大房子不能去拿小房子的东西,这就是所谓的作用域。

​ 在一个函数function a() {}中,这个a函数会存在很多属性,但是有一个属性,我们时没有办法访问到的,那就是——[[scope]]:

​ 那这个[[scope]]是干什么的呢,我们就可以简单的理解成它就是作用域,存放着种种的嵌套关系的一个变量。在了解这个[[scope]]之前,我们要了解一下执行期上下文的概念,那么什么是执行器上下文呢?所谓的执行器上下文就是我们之前说的预编译的环节,当函数之前的前一刻,生成AO对象那四个步骤。当函数定义的时候,会先在[[scope]]这个属性里,先生成一个栈,这个栈就是后来我们经常说的作用域链,在定义的时候,会在该栈推入一个GO(全局执行器上下文),当执行这个函数的时候,会在原有的栈的基础上,接着推入该函数自己的执行期上下文(AO),如果存在函数嵌套关系以此类推,说了这么多,还是不知道啥玩意儿?没关系!我们直接上代码,并结合流程图分析一下就明白了。

​ 我们在这里来举一个例子:

function a() {
    var a = 123;
    function b() {
        var b = 234;    
    }
    b();
}
a();

如上代码所示,我们声明了一个a函数,在a函数的内部又声明了一个变量a和一个b函数,在b函数中声明了一个变量b,然后在a函数中执行了b 函数,再最外层执行了a函数。那么这个时候JS引擎可做了不止这么多的事,接下来我们配合流程图来对这段代码一步一步的执行一下,相信我,你会对JS有一个重新的认识!

  1. 声明a函数的时候被称为a函数的定义,系统则会在 [[scope]] 属性上挂载一个类似于数组(其实就是栈)一样的东西,然后在这个数组中的第0位保存全局执行器上下文GO以及其他各种全局的变量,例如window对象,document对象等, 如下图所示:

    image-20201225162436941

  2. 这个时候仅仅是a函数的定义,接下来就是a函数的执行,同样的,a函数会生成自己的执行期上下文AO ,其内部包含了a变量以及b函数等对象,同时往栈内压入a函数的AO对象:

    image-20201225163132455

  3. a函数的执行的时候,遇到了b函数的定义,在定义的时候b函数保留了a函数的所有的“遗传信息”,所以可以理解为b函数 拷贝了一份执行期上下文当作自己的执行器上下文:

    image-20201225164043286

  4. 我们可以这么理解b函数是站在a函数的角度去看世界的,所以b函数一出生就有个很好的环境(啥都有),这个时候b函数执行,生成了自己的执行期上下文bAO,并推入数组的首位:

    image-20201225164247981

由上述我们可以知道,原来简单的一个函数嵌套函数也有这么复杂的逻辑保留在JS引擎中,最后我们再总结一下:

函数运行状态 作用域 保存的值
a函数执行时: [[scope]] scopeChain[0] : aAO
scopeChain[1] : GO
b函数定义时 [[scope]] scopeChain[0] : aAO
scopeChain[1] : GO
b函数执行时 [[scope]] scopeChain[0] : bAO
scopeChain[1] : aAO
scopeChain[2] : GO