一个 JavaScript 文件从下载到执行发生了什么

前言

JavaScript 的执行相信很多人都已经不陌生了,毕竟在程序的世界你天天都在调试。我也相信你已经知道 JavaScritp 有预编译这么一说。但在执行前后的预编译做了什么你知道吗?下面我们就来揭开它的神秘面纱。

我们就从 JavaScript 文件从无到有,再到执行完作为线索来一步一步的深入理解JavaScript 的预编译与执行。

首先当我们打开一个页面时,开始下载 JavaScript 文件,如果有多个的话就会依次下载。这是常识,但在常识的背后究竟做了些什么,可能这才是我们最关心的。

JavaScript 文件揭秘之旅

首先下载第一个文件,第一个文件下载完之后还不会直接去下载第二个 JavaScript 文件,而是去对下载完的第一个 JavaScript 文件里的代码进行以下处理:

1、语法分析

2、编译

3、执行

语法分析:没什么好说的,就好比我们英语中的语法,按 JavaScript 的编程套路出牌并且浏览器认就行。

编译:这个说来话长,下面我们一步一步分解。

1、当代码通过了语法分析后,就进入了编译阶段。

2、创建一个全局对象 window。

3、查找全局(不含局部)中的变量声明,如果有就把变量名作为 window 对象属性名,并赋值 undefined。

4、查找全局(不含局部)中的函数声明,如果有就把函数名作为 window 对象属性名,并且值为此函数体。

比如我们有下面这么一段代码:

var a = "云库笔记";
var b = "云库前端";
function c(x){
    var d = "博客";
    console.log(x);
    function e(){
        console.log("d");
    }
}
c(1);

预编译完后 window 对象下就会多出以下属性

window = {
    a : undefined,
    b : undefined,
    c : function c(x){......}
}

编译完后就开始从上往下执行,执行无后:

window = {
    a : "云库笔记",
    b : "云库前端",
    c : function c(x){......}
}

当执行到 c(1),在 c(1) 执行之前,又会进行一次编译。也是跟上面的差不多,只不过此时 c(1) 方法中的变量声明或者函数声明不是挂在 window 下了,而是挂一个活动对象(AO)下,但原理还是一样的。

在执行 c(1) 之前的编译步骤如下:

1、创建活动对象 AO

2、 查找形参和变量声明,赋值 undefined

3、实参赋值给形参

4、查看函数声明,并且值为此函数体。

步骤1 和 步骤2 后:

AO = {
    x:undefined,
    d:undefined,
}

步骤3 和 步骤4 后:

AO = {
    x:1,
    d:undefined,
    e:function e(){......}
}

编译完成后执行:给变量 d 赋值,打印出 x 的值。

注意:最后面的函数 e 会被忽略,只有当函数 e 被调用时才会像函数c 一样走先编译再执行。

AO = {
    x:1,
    d:"博客",
    e:function e(){......}
}

至此文件也应该结束了,但我们似乎忽略了一个不常见却很有可能被自己造出来的坑坑到的场景。

代码如下:

console.log(a);
var a = "云库笔记";
console.log(a);
var b = "云库前端";
function c(){};
function a(){};
console.log(a);

上面这段代码你猜三个console 各自会打印出什么?不卖关子:

第一个打印出function a(){}

第二和第三个打印出云库笔记

这里我们就需要注意了,我们回想一个当编译遇到一个函数声明时是怎么处理的?

当编译遇到变量声明时,变量名作为 AO 对象的属性并赋值 undefined ,而当编译遇到函数声明时同样是以函数名作为 AO 对象的属性,但是它的值不是 undefined,而是此函数体。

console.log(a);
var a = "云库笔记";
console.log(a);
var a = "云库前端";
console.log(a);

如果变量已经被声明,再次声明此变量时 var 直接被忽略,JavaScript 引擎会把它当作是一个赋值操作。

所以打印出来的结果为 undefined,云库笔记,云库前端。

这里还有一点需要注意的是类似这样的:var a = function(){} 是函数表达式,而不是函数声明。编译时它跟变量声明的处理方式是一样的。

当上面第一个 JavaScript 文件处理完后,就会云下载第二个 JavaScript 文件。然后再重复上面的步骤。