Javascript从作用域,作用域链到预编译

248 阅读4分钟

作用域

什么是作用域呢?通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。在javascript中作用域有以下几种:

1.函数作用域

2.全局作用域

我们用一张图来看一下

scope.png 图片中我们清楚地可以看到全局作用域代码产生时就会出现,而函数作用域是在函数体内部的。

3.块级作用域

说到块级作用域就需要我们知道其实js在2015年进行了一次更新,出现了let,const 定义变量的语法,而块级作用域的出现就需要letconst定义一个变量再加上一个{},举个简单的例子:

for(let i=0; i<10; i++){}

在这里其实就形成了快级作用域。

注意

内层作用域可以访问外层作用域的,反之则不可以;举个例子:

图.png

我们可以清楚地看到全局作用域被函数作用域给访问了,而反过来就不可以。

作用域链

相信看到这里大家应该都了解了作用域,那什么是作用域链呢?

在这之前我们应该先要理解一个概念:运行期上下文:指的是当函数执行时,会创建一个称为执行期上下文的内部对象,简称AO:{};多次调用就会多次创建,当函数执行完毕就会销毁。

当然全局也有内部对象,简称GO:{}

还有点抽象?我们来看代码:

function a() {
    function b() {
        var b = 2
    }
    var a = 1
    b()
}
var glob = 100
a()
//a 定义  产生全局内部对象 ---> 0 : GO{}
//a 执行  产生函数内部对象 ---> 0 : AO{}  1: GO{}

内部对象里面有什么呢?看图:

图1.png

函数a定义时会产生一个全局作用域[[scope]],全局作用域里面有全局对象,包括定义的数值。

当a执行时,看图:

图2.png

a执行会产生一个执行期上下文的内部对象AO:{},它会挤在GO:{}的前面。 而作用域链就是[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,故叫做作用域链

预编译

声明提升

1.在编译时,将变量的声明,提升到当前作用域的顶端

 console.log();
 var a = 1

此段代码在编译时相当于

 var a
 console.log()
 a = 1

打印的结果都为undefined。

2.函数声明整体提升

同样函数的声明函数整体都会提升

foo();
function foo() {
     var a = 2;
     console.log(a);
 }

相当于

 function foo() {
     var a = 2;
     console.log(a);
 }
 foo();

都能打印出结果为2。

函数执行之间(四部曲)

  1. 创建一个AO对象
  2. 找形参和变量声明,将变量声明和形参作为AO的属性名,值为underfined
  3. 将实参和形参统一
  4. 在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体

我们来看下面的代码,分析它的函数执行之前:

var a = 1;
function foo(a) {
    var a = 2;
    function a() { }
    var b = a
    console.log(a);

}
foo(3);

当foo函数要执行时

第一步:产生一个执行上下文对象AO:{}

第二步:形参为a,变量声明有a和b作为属性名,值为undefined。此时 AO:{a: undefined, b: undefined}

第三步:实参为3,为a的值 AO:{a: undefined 3 , b: undefined}

第四步:有函数声明a,值赋予函数体AO:{a: undefined 3 function(){} , b: undefined}

之后再执行函数,最终:AO: {a: undefined 3 function a() { } 2, b: undefined 2}打印出来a的值为2。

全局执行(分三步)

  1. 创建一个GO对象
  2. 找变量声明,将变量声明作为GO的属性名,值为undefined
  3. 在全局找函数对象,将函数名作为GO的属性名,值赋予函数体

综合上面讲到的知识,我们再来分析一下下面代码:

global = 100
function fn() {
    console.log(global);
    global = 200
    console.log(global);
    var global = 300
}
fn()
var global;

首先声明提升,创建一个GO:{}

接下来找变量声明,全局下有global,GO:{global:undefined}

然后找函数对象,全局下有fn函数,GO:{global:undefined, fn: function(){}}

最后执行函数调用又回到了函数执行之前的四部曲

这里简略一下得出AO: {global: undefined 200 300}}

第一个console.log()打印出undefined,第二个打印出200 。

小结

JS代码到浏览器里去执行时,V8引擎会按照自己的规则将JS编译执行,通过学习作用域,作用域链和预编译让我们对V8引擎怎样运行JS清楚了,本文可能存在不足,欢迎大佬批评指正。