• 我们在书写js代码的时候,会发现两点和c/c++语言不同的地方。第一个是当我们在后面定义了一个函数之后,我们在定义函数之前使用这个函数也是可以的。第二个是我们在后面声明的一个变量,但是在前面调用这个变量的时候并不会报错而是undefiend。
这两点不同在js中被称为函数声明提升和变量声明提升,函数声明提升是一种整体提升,它会把函数声明和函数体一起提升到前面。变量声明提升则是一种局部提升,它仅仅将变量的声明提前了,但是并没有将赋值也一起提前。
那么为什么会出现这种提升的现象呢?
这是因为js运行的时候有一个阶段叫做预编译阶段,而我们的声明提升现象都是发生在预编译的时候哟~
预编译
• js运行三部曲
1.语法分析
2.预编译
3.解释执行
语法分析:js引擎在解析js代码之前,会先通篇扫描一下,找出低级的语法错误,比如写错大括号之类的。
编译执行:我们前面提到js是一种解释型语言,编译一行执行一行,当语法分析没有问题,并且已经完成预编译阶段之后,就开始解释执行代码。
这里我们着重介绍预编译。
预编译前奏
在介绍预编译之前,我们有两个重要概念需要掌握。
1.imply global 暗示全局变量。
如果任何变量未经声明就赋值使用,此变量就会为全局对象window所有,并且成为window对象的一个属性。
1. window.a = 123;
2. window.a === a // true;
或者
1. a = 123;
2. window.a===a // true
2.一切声明的全局变量,都是window的属性。
1. var a = 123;
2. console.log(window.a); // 123
• 这样看不论全局变量有没有声明,似乎都会成为全局对象上的属性,那么两者之间有什么区别呢?
区别在于:经过声明的全局变量不能通过delete操作来删除,但是未经声明的全局变量可以被删除。
1. a = 123;
2. console.log(window.a === a) // true
3. delete window.a;
4. console.log(window.a); // undefiend
5. var b = 123;
6. delete window.b;
7. console.log(window.b); // 123
正是这一种特性,导致es5有一种弊端,我们总会在无形中声明一些全局变量。
1. function test () {
2. var a = b = 0;
3. }
这段代码的原意是:在函数体中声明两个变量a、b,然后初始化a、b都是0。但是我们这么写之后,a经过了声明,但是b却没有声明,这时候b就会成为一个全局变量。
了解这两点之后,我们正式介绍一下预编译的过程。
预编译的过程我总结为以下四步:
1.创建AO对象。
2.寻找形参和变量声明,将变量和形参作为AO对象的属性名添加到对象中,值为undefined。值得注意的是,函数声明不叫变量。
3.将实参值和形参值相统一。
4.在函数体里面寻找函数声明,将函数名作为属性名,值为这个函数的函数体。
函数在执行的前一刻会产生一个上下文,这个上下文就是Activeaction Object对象,简称AO对象。
AO = {}
这个对象是空的,但是里面有一些我们看不到的却存在的隐式属性,比如this: window属性和arguments: [];属性
这个对象用来存放一些属性和方法,这些属性和方法就按照前面的四步来产生。
1. function test (a, b) {
2. console.log a
3. function a () {}
4. a = 222;
5. console.log a
6. function b () {};
7. console.log b
8. var b = 111;
9. var a ;
10. }
11. test(1)
这里我们用这一个样例代码来简单介绍一下预编译的过程。
首先第一步,创建一个AO对象。
var AO = {};
第二步,寻找形参值和变量声明,并且将值赋为undefined。
1. AO = {
2. a: undefined,
3. b: undefined
4. }
3.将实参值和形参值相统一。这里因为属性名都已经存在了,所以直接赋值就可以了。
1. AO = {
2. a: 1,
3. b: undefined
4. }
4.寻找函数声明,将函数体赋值给属性。
1. AO = {
2. a: function () {},
3. b: function () {}
4. }
这样在编译执行之前,我们预编译阶段创建的AO对象就是这个样子了,这个时候我们再看看分别打印的值是什么。
第一个console.log a –> function () {}
第二个console.log a –> 222 因为执行了a = 222这一行代码,所以重新赋值了。
第三个console.log b –> function () {}
• var b = function () {}这种不叫做函数声明,这个函数是赋值给b变量的,b变量是声明。
1. function fn(a){
2. a//function
3. d//function
4. var a = 123;
5. a//123
6. function a () {}
7. b//undefined
8. var b = function () {}
9. b//function
10. function d (){}
11. }
这里的var b = function () {}只是声明了b变量,在第四步寻找函数声明里面并不会把b赋值成function () {},因为后面的函数并不是声明,当代码开始解释执行之后,执行到这一行之后才把b赋值成这个函数。
• 寻找变量声明的时候,不会管里面的代码到底会不会执行,执行是后面的事,这里只负责寻找所有变量。
1. funciton test(b) {
2. a //undefined
3. if(1 > 5){
4. var a = 123;
5. function b () {}
6. }
7. a//undefined
8. b//function
9. var b = 234;
10. b// 234
11. }
12. test(2);
打印第一个a的时候并不会报错而是undefined,当a没有声明的时候才会报错,因此这里a是有声明的,只是没有赋值而已,它根本不看有没有if,if的条件是不是真对寻找变量声明都没有关系。
第二步寻找形参和变量声明时候的AO对象:
1. AO = {
2. a: undefined, //这里的a虽然在if里面但是也被声明了
3. b: undefined
4. }
以上就是我总结的预编译的知识点,希望对大家有帮助哟!~