JavaScript学习笔记(五):预编译

0 目录

  • 预编译前菜
    • 全局变量的知识补充
    • 什么是预编译?
    • 预编译会造成什么现象?
  • 预编译的过程
  • 预编译在函数执行过程中的体现
  • 全局作用域里的预编译
  • JavaScript整体执行的预编译

1 预编译前菜

全局变量的知识补充

  • 暗示全局变量
    即任何变量,如果未经声明就赋值,则此变量为全局对象所有(即全局变量)
function func() {
    a = "a";
    var b = "b";
}
func(); //函数执行后,变量声明和赋值才能生效
console.log(a); //a未经声明就赋值,属于全局变量,在函数外部可被访问
console.log(b); //b是局部变量,在函数外部不能被访问
  • 一切声明的全局变量,都是window对象的属性
    window是全局对象,也是全局的域
    全局变量再声明后,会自动成为window对象的一个属性,访问时可直接通过全局变量的变量名访问,也可以通过 window.变量名 的方式访问
var a = 123;    //等同于在window对象上加上了一个属性:window{ a : 123 }
//下面两种访问方式的结果都是一样的
console.log(a);
console.log(window.a);

什么是预编译?
JavaScript的执行过程大致分为三步:

  • 语法分析
  • 预编译
  • 解释执行

预编译是JavaScript执行过程中的一个重要步骤

预编译会造成什么现象?
在其他高级语言中,基本上变量和函数都是先声明再使用,如C/C++中,变量和函数的声明一定是写在变量和函数被使用之前,否则就会报错。
而在JavaScript中,变量和函数在声明之前被使用时,不会报错,且函数在声明之前被使用是完全允许的。但不是说变量和函数不声明就可以使用,还是要声明才能正常使用的,只不过声明可以写在使用之后。这就是JavaScript预编译造成的现象。
下面四段代码可以进行演示预编译造成的现象:

var a = 123;
console.log(a);

很正常的一段代码,输出结果为:123
 

console.log(a);
var a = 123;

不会报错,但执行结果可能并不是我们想要的,输出结果为:undefined
 

function test(){
    console.log("123");
}
test();

同样是一段很正常的代码,输出结果为:"123"
 

test();
function test(){
    console.log("123");
}

不会报错,且函数正常执行,输出结果为:"123"
 
以上是预编译所造成的现象,后面将对这些现象进行解释

2 预编译的过程

预编译的过程可以概括为四个步骤:

  • 创建AO对象(Activation Object):
AO{ }
  • 找形参和变量声明,将形参名和变量名作为AO对象的属性名加入到AO对象里,并将其赋值为undefined
AO{
    形参名:undefined,
    变量名:undefined
}
  • 将实参值和形参值统一(实参值赋值给AO对象里的形参):
AO{
    形参名:实参值,
    变量名:undefined
}
  • 在函数体里面找函数声明,将函数名加入AO对象,赋值为函数体
AO{
    形参名:实参值,
    变量名:undefined,
    函数名:函数体
}

注:AO对象里不会出现多个相同名字的属性。若变量名、形参名和函数名一样时,AO对象里的属性值则会被覆盖为最新的一次赋值。

3 预编译在函数执行过程中的体现

函数执行过程中的预编译发生在函数执行的前一刻
举例:

function fn(a){
    console.log(a); //输出结果为function a(){}
    var a = 123;
    console.log(a); //输出结果为123
    function a(){}
    console.log(a); //输出结果为123
    var b = function(){}
    console.log(b); //输出结果为fucntion(){}
}
fn(1);

在上述代码的预编译过程及执行过程的分析如下:
预编译阶段:

  1. 创建AO对象:AO{}
  2. 找到形参和变量声明,将形参名和变量名放入AO对象,赋值为undefined。若存在同名的,只存储一个。
AO{
    a: undefined,
    b: undefined
}
  1. 将实参值和形参值统一:
AO{
    a: 1,
    b: undefined
}
  1. 找函数声明,将函数名加入AO对象,赋值为函数体:
AO{
    a: function a(){},
    b: undefined,
}

函数执行阶段:

  1. 第一个console.log(a);语句:从AO对象里读取a,输出结果为function a(){}
  2. var a = 123;语句:此时a已被声明,因为形参里也有一个a,所以a已经被隐式地声明过了。这里略过声明,直接执行赋值操作,将AO对象里的a赋值为123:
AO{
    a: 123,
    b: undefined
}
  1. 第二个console.log(a);语句:再次从AO对象里读取a,输出结果为123
  2. function a(){}语句:此语句直接跳过,不执行,因为在预编译阶段已经完成了函数声明(在AO对象里)
  3. 第三个console.log(a);语句:再次从AO对象里读取a,输出结果为123
  4. var b = function(){}语句:这里的声明已经在预编译阶段完成,所以跳过声明,直接执行赋值操作,将AO对象里的b赋值为function(){}:
AO{
    a: 123,
    b: function(){}
}
  1. 第四个console.log(b);语句:从AO对象里读取b,输出结果为function(){}

4 全局作用域里的预编译

在全局作用域里同样也存在预编译,其过程和函数执行过程中的类似,只是少了"将实参值和形参值统一"这一步,因为全局作用域里不存在参数这个概念。
还有一点区别就是,全局作用域里生成的不是AO对象而是GO对象(Global Object),其作用与AO对象的一致。
GO对象其实就等效于window对象。

5 JavaScript整体执行的预编译

就是函数作用域和全局作用域同时存在时预编译的过程
此时,先生成GO对象并完成全局作用域的预编译过程,然后再生成AO对象并完成函数作用域的预编译过程。
在函数执行阶段,若有函数体里的局部变量与某一个全局变量重名,则优先访问函数体里的变量值(即AO对象里对应的属性值)

posted @ 2020-05-03 23:29  xiaowus  阅读(131)  评论(0编辑  收藏  举报