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);
在上述代码的预编译过程及执行过程的分析如下:
预编译阶段:
- 创建AO对象:
AO{}
- 找到形参和变量声明,将形参名和变量名放入AO对象,赋值为undefined。若存在同名的,只存储一个。
AO{
a: undefined,
b: undefined
}
- 将实参值和形参值统一:
AO{
a: 1,
b: undefined
}
- 找函数声明,将函数名加入AO对象,赋值为函数体:
AO{
a: function a(){},
b: undefined,
}
函数执行阶段:
- 第一个
console.log(a);
语句:从AO对象里读取a,输出结果为function a(){}
var a = 123;
语句:此时a已被声明,因为形参里也有一个a,所以a已经被隐式地声明过了。这里略过声明,直接执行赋值操作,将AO对象里的a赋值为123:
AO{
a: 123,
b: undefined
}
- 第二个
console.log(a);
语句:再次从AO对象里读取a,输出结果为123
function a(){}
语句:此语句直接跳过,不执行,因为在预编译阶段已经完成了函数声明(在AO对象里)- 第三个
console.log(a);
语句:再次从AO对象里读取a,输出结果为123
var b = function(){}
语句:这里的声明已经在预编译阶段完成,所以跳过声明,直接执行赋值操作,将AO对象里的b赋值为function(){}
:
AO{
a: 123,
b: function(){}
}
- 第四个
console.log(b);
语句:从AO对象里读取b,输出结果为function(){}
4 全局作用域里的预编译
在全局作用域里同样也存在预编译,其过程和函数执行过程中的类似,只是少了"将实参值和形参值统一"这一步,因为全局作用域里不存在参数这个概念。
还有一点区别就是,全局作用域里生成的不是AO对象而是GO对象(Global Object),其作用与AO对象的一致。
GO对象其实就等效于window对象。
5 JavaScript整体执行的预编译
就是函数作用域和全局作用域同时存在时预编译的过程
此时,先生成GO对象并完成全局作用域的预编译过程,然后再生成AO对象并完成函数作用域的预编译过程。
在函数执行阶段,若有函数体里的局部变量与某一个全局变量重名,则优先访问函数体里的变量值(即AO对象里对应的属性值)