JS-作用域和作用域链

314 阅读4分钟

AO 和函数紧密相关

函数其实也是一种对象类型/引用类型/引用值,有自己的属性,比如 test.name / test.length / test.prototype

但是对象有些属性是我们无法访问的,就是JS引擎内部固有的隐式属性(私有属性),[[scope]]域 就是其中之一。

[[scope]]

  1. 是函数创建时,生成的一个JS内部的隐式属性,只能由JS引擎来读取

  2. 是函数存储作用域链的容器,作用域链存储的就是AO/GO

    • AO(函数的执行期上下文),函数执行完成以后,AO是要销毁的(每一次执行函数时,会生成新的AO),也就是说AO是一个即时的存储容器。

    • GO(全局的执行期上下文)。

作用域链就是将AO/GO排列起来,形成一个链式关系

实例说明

function a(){
    function b(){
        var b = 2;
    }
    var a = 1;
    b();
}
var c = 3;
a();

当a函数被定义时

function a(){}
var c = 3;

系统生成[[scope]]属性

[[scope]]保存该函数的作用域链

函数a function a(){}
[[scope]]scope chain 作用域链

该作用域链的第0位存储当前环境下的全局执行期上下文GO

scope chain 作用域链
0GO全局执行期上下文

GO里存储全局下的所有对象

GO
thiswindow
window(object)
document(object)
a(function)
c3

当a函数被执行时(前一刻)-- 预编译

function a(){
    function b(){} //b函数还没有执行,里面不看
    var a = 1;
}
var c = 3;
a();

作用域链的顶端(第0位)存储a函数生成的函数执行期上下文AO,同时第1位存储GO

查找变量是0往下找

[[scope]]-> scope chain保存该函数的作用域链

函数a function a(){}
[[scope]]scope chain
scope chain 作用域链
0AO
1GO

0 -> AO

AO
thiswindow
arguments[]无参数
a1
b(function)

1 -> GO

GO
thiswindow
window(object)
document(object)
a(function)
c3

当b函数被定义时

在a函数(上级)环境下,所以 b函数被定义时的作用域链 = a函数被执行时的作用域链

因为b还没有被执行,所以看到的东西和上级执行完看到的东西一样

当b函数被执行时

function a(){
    function b(){
        var b = 2;
    }
    var a = 1;
    b();
}
var c = 3;
a();

生成函数b的[[scope]],存储函数b的作用域链

函数b function b(){}
[[scope]]scope chain

顶端第0位存储b函数的AO

a函数的AO和全局的GO依次向下排列

scope chain 作用域链
0函数b的AO
1函数a的AO
2全局的GO

0 -> 函数b的AO

函数b的AO
thiswindow
arguments[]无参数
b2

a函数的AO和全局的GO不变(引用)

当b函数被执行结束时

b函数的AO被销毁,回到被定义的状态

当a函数被执行结束时

a函数的AO被销毁的同时

b函数的[[scope]]也不复存在(因为b函数存储在a函数的AO里,整个函数都不存在了)

a函数回归被定义时的状态


总结:

全局函数被定义时,也就是全局在执行时,生成作用域和作用域链(因为每个函数都必须要有GO)

函数被执行时那一刻,生成自己的AO,同时内部函数被定义(拿上级的环境作为基础)

被销毁的时候,AO销毁

上层AO销毁时,该函数的[[scope]]也没了

每次执行时,都会生成新的AO

例:

function a(){
    function b(){
        function c(){}
        c();
    }
    b();
}
a();

a定义:a.[[scope]] -> 0: GO
a执行:a.[[scope]] -> 0: a -> AO
                      1: GO
b定义:b.[[scope]] -> 0: a -> AO
                      1: GO
b执行:b.[[scope]] -> 0: b -> AO
                      1: a -> AO
                      2: GO
c定义:c.[[scope]] -> 0: b -> AO
                      1: a -> AO
                      2: GO
c执行:c.[[scope]] -> 0: c -> AO
                      1: b -> AO
                      2: a -> AO
                      3: GO
c结束:c.[[scope]] -> 0: b -> AO
                      1: a -> AO
                      2: GO
b结束:b.[[scope]] -> 0: a -> AO
                      1: GO
       c.[[scope]] ×
a结束:a.[[scope]] -> 0: GO
       b.[[scope]] ×

所以外部访问不到内部的变量的原因就是,外部的作用域链,没有内部函数的AO

函数不执行的话,不会看函数里面的值,不会生成AO,相当于只存了个名号