logo头像
Snippet 博客主题

用预编译去理解函数声明提升和变量声明提升

在我们前端笔试的过程中几乎都会遇到函数声明提升或者变量声明提升的题目,是不是很多人知道这个知识点但是分析的时候还是会出现不同程度的错误呢,接下来跟我一步步走下去,让大家以后再也不用担心这类题目带来的困扰。

1、js运行三部曲

我们必须要了解javascript有2个特点,单线程和解释性语言,可以理解解释性语言是翻译一句执行一句,那么在js运行时是经过以下三步:

  • 语法分析
  • 预编译
  • 解释执行
说明:语法分析就是通篇扫描一遍是否有语法错误,没有错误进行预编译阶段,然后进行解释执行。

2、预编译前奏

  • imply global 暗示全局变量: 即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有;
  • 一切声明的全局变量,全是window的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

eg: 这里只是简要说明下以上亮点。
console.log(window.a)
console.log(window.b)
function testGlobal(){
var a = b = 123;
}
testGlobal()
console.log(b)
console.log(a)

eg: 按照我们思维解释一行编译一行,没有任何问题。
function test(){
console.log("test")
}
test() // test

eg: 按照我们思维解释一行编译一行,应该报错呀,为什么能正常打印出结果呢。
test1() // test1
function test1(){
console.log("test1")
}

eg:
var test3 = 5
console.log(test3) // 5

eg: 为什么能输出undefined
console.log(test4) //undefined
var test4 = 6

eg: 为啥这样就报错。
console.log(test5) // error

经过以上的例子,很多人会快速的总结两句话:

  • 1,函数声明整体提升、
  • 2,变量的声明提升。

这2句正确但是不能解决我们所有问题,不多说,用例子说话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

eg: 这里我就不写答案了,自己可以试一试是否掌握。
console.log(a);
function a(a){
console.log(a);
var a = 123;
var a = function(){};
console.log(a);
}
a(123);
var a = 123;

eg: 这里我就不写答案了,自己可以试一试是否掌握。
console.log(a);
function a(a){
console.log(a);
a = 123;
function a(){};
console.log(a);
}
a(123);
var a = 123;

如果感觉有点晕乎,那么我们了解预编译则可以轻松解答问题。

3、预编译

记住预编译发生在函数执行的前一刻,我们记住以下四部曲即可:

  • 创建AO对象;
  • 找形参和变量声明,将变量和形参作为AO对象的属性名,值为undefined;
  • 将实参值和形参统一;
  • 在函数体里面找到函数声明,值赋予函数体。

那么有人会问了,函数执行是这样,那么全局定义的变量声明和函数声明会是怎样呢,其实一样,不同的就是第一步,全局创建GO对象,GO也就是等于window。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

eg: 我们先看函数执行
function fn(a){
console.log(a);
console.log(b);
console.log(c);
var a = 123;
function a(){};
console.log(a);
var b = function(){};
console.log(b);
function c(){};
}
fn(111);

求解:对应以上步骤来
1步: 创建AO对象
AO{

}
2步: 找形参和变量声明,将变量和形参作为AO对象的属性名,值为undefined,如果形参和变量声明或者函数声明有同名的则写一个就好。
AO{
a: undefined,
b: undefined,
c: undefined
}
3步: 将实参值和形参统一;
AO{
a: 111,
b: undefined,
c: undefined
}
4步: 在函数体里面找到函数声明,值赋予函数体。
AO{
a: function a(){},
b: undefined,
c: function c(){}
}

5步: 在按照顺序执行,提升的和赋值过的我们就不用看了。
fn(111);
console.log(a) // function a(){} a ==> AO.a
console.log(b) // undefined
console.log(c) // function c(){}
a =123 // 这里只剩下赋值 A0.a = 123
console.log(a) // 123
b = function(){}; // AO.b = function(){}
console.log(b) // function(){}

总结说下第五步,这就是剩下执行的语句了,为啥变量声明和函数声明都没有了呢,就是前面4步已经执行了,这下是不是有点恍然大悟,原来如此简单,我们继续多来几个例子来说明,也便于增加记忆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

eg:
function fn1(a,b){
console.log(a);
console.log(b);
var b =234;
a = 123;
console.log(a);
function a(){};
var a;
b = 456;
var b = function(){}
console.log(a);
console.log(b);
}
fn1(222);
按照预编译步骤来解:
1步:
AO{}
2步:
AO{
a: undefined,
b: undefined,
}
3步:
AO{
a: 222,
b: undefined,
}
4步:
AO{
a: function a(){},
b: undefined,
}
5步: 解释执行
fn1(222);
console.log(a) // function a(){}
cosnole.log(b) // undefined
b = 234; // AO.b = 234
a = 123 // AO.a = 123
console.log(a) // 123
b = 456; // AO.b = 456
b = function(){} // Ao.b = function(){}
console.log(a); // 123
console.log(b); // function(){}

现在经过以上2个案例是不是基本了解预编译,是不是就不怕函数声明提升和变量声明提升呢,有人说了,要是全局变量声明和有全局函数声明又有函数体内函数声明顺序又是怎么样的呢?

记住全局生成GO对象,函数执行体生成AO对象。其他规则一样,继续来例子说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

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


1步:
GO{}
2步:
GO{
global: undefined
}
3步:, 没有
4步:
GO{
global: global(){ ...}
}
5步:解释执行
console.log(global) // global(){ ...}
接下来要执行函数,那么执行函数之前要走4
1步:
AO{}

2步:
AO{
global: undefined
}
3步: 没有
4步: 没有

5步:解释执行
global()
console.log(global) // undefined 注意:AO对象找到则值为AO的属性值,没有则去GO对象里面去找
global = 200 // AO.global = 200
console.log(global) // 200
global = 300 // AO.global = 300
global = 100; // GO.global = 100
console.log(global) // 100 这里是全局的global

eg: 这一题有了if,其实预编译我只管找你声明,其他不管,那么又和以上步骤相同,还有就是没有声明就赋值则挂在GO对象上
function test(){
console.log(a); // undefined AO对象没有,则到GO对象中找
console.log(b); // undefined
if(a){
var b = 100; // 不执行b的赋值
}
console.log(b); // undefined
c = 234;
console.log(c) //234
}
var a ;
test();
a = 10;
console.log(c) //234

解答,这里我就简要的写最终两个对象的值:
GO{
a: 10
c: 234
}
AO{
b: undefined,

}
所以答案很明显.

4、总结

经过以上总结,那我们是不是可以找到规律:

  • 打印变量值前面有赋值,则该值为赋值的值。
  • 打印变量值之前没有赋值,有函数声明则值函数声明,没有再看形参,再没有看变量声明,从AO找到GO。说白了就是4步曲的倒序。
微信打赏

赞赏就是肯定