关于JavaScript预编译到作用域链

407 阅读4分钟

前言:

我作为一个js还没进门的小白来说,常常用自己的思维区写代码的时候会出现各种各样的问题,要么运行不了,要么就是结果和预期的不一样。当我认识到了浏览器的编译原理和作用域关系的时候,代码运行不出来这种低级错误就不会再有了,这也能更好地形成一个代码的编写规范。这也是我为什么写这篇文章的原因。

我们看看下面这个代码:

        function fn(a){
            console.log(a)
            var a = 200
            console.log(a)
            function a(){}
            console.log(a)
            var b = function(){}
            console.log(b)
            function d(){}
            var d = a
            console.log(d)
        }
        fn(1)

是不看得眼花缭乱?

让我们用函数体内预编译的四个步骤,手动编译一下就清楚了

#发生在函数体内执行之前 四部曲

1. 创建一个AO对象(Activation object)

2. 找形参和变量声明,将变量声明和形参作为AO的属性名,值为undefined

3. 将实参和形参统一

4. 在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体

经过完上述的四个步骤就可以由上到下逐句执行

第一步我们先创建一个AO对象

AO:{}

第二步我们再到函数体中找到变量的声明和形参放到AO中,值未undefined

此处需要注意:var a = 200被分为两句,声明var a;在进行赋值a = 200。

AO:{a:undefined,
    b:undefined,
    d:undefined,
}

第三步将实参形参统一

a被赋上了1

AO:{a:1
    b:undefined
    d:undefined
}

第四步找函数声明,将函数名作为AO对象的属性名,值赋予函数体

AO:{a:function a(){}
    b:undefined
    d:function(){}
}

a的值变成了函数体;d的值也变成了函数体

最后我们开始执行

第一行打印了a:function a(){};

第二行给a赋值:a = 200;

第三行打印了a:200;

第四行函数不执行;

第五行打印了a:200;

第六行赋值在AO中给b赋值:b = function(){};

第七行打印b:function(){};

第八行函数不执行;

第九行给d赋值:d = a;

第十行打印d:200;

附上结果图:

捕获.PNG

所以我们看见在js编写规范中顺序排列还是非常重要的。


那么在全局下的执行前预编译是怎么样的呢?

  1. 创建全局对象GO(global object)

  2. 找全局变量声明,将变量声明作为GO的属性名,值为undefind

  3. 在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体 (比函数体内少一个形参统一)

这里就不一一介绍啦,

看到这里的同学可以去尝试全局的预编译


上面我们提到了,函数的作用域就是AO(Activation object)对象;全局的作用域是一个GO(global object)对象。

当函数执行的时候,会创建一个执行期上下文的内部对象叫AO对象,一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致函数创建多个执行期上下文,当函数执行完毕它所产生的执行期上下文会被销毁。

每个函数被创建的时候会附带一个[[scope]]属性,这就是该函数作用域的属性,其中存储了运行期上下文的集合。当调用这个函数的时候后,会创建AO对象来储存它的参数属性并保存在作用域链上,同事会创建一个更长的表示函数来调用作用域的链。当函数嵌套时,调用外部函数,所产生的作用域链式不同的。

属于隐藏式属性无法访问的。在[[scope]]中呈链式连接的。我们把这种链式连接叫做作用域链。

var d = 20

function a() {
    var c = 10
    console.log(d)
    function b() {
        e = 30
        console.log(c)
    }
    b()
}
a()

当执行到函数a的时候

捕获1.PNG

当执行到函数b的时候

捕获.PNG 函数b中的console.log(c)在b的AO对象中找不到时,会在函数a的AO对象中查找,在作用域链中是向下查找。

为什么外层无法访问内层函数的作用域?

外层无法访问内层函数的作用域,因为在内层函数执行玩后该函数的AO对象会进行内存回收。