浏览器解析JS脚本执行顺序问题
有些朋友可能会觉得JavaScript的代码是从上到下,一行一行的解释执行的。如果按照这样的思路,在有些情况下阅读代码会得到错误的结果,考虑以下代码:1
2
3a = 2;
var a;
console.log(a);
console.log(a)应该输出什么呢?有些开发者觉得会输出undefined,因为var a在‘a = 2’之后,变量 a 被重复定义了,但是没有被赋值,所以是’undefined’。但是结果输出是 2。如下图所示:
我们再来考虑另一段代码,如下所示:1
2console.log(a);
var a = 2;
这段代码会输出什么样的结果呢?有些人可能会觉得输出ReferenceError。因为变量a在没有声明的情况下就被使用了。真实结果呢,如下图所示:输出的是undefined
为什么会这样呢?这就牵出了本文的主题:JavaScript声明提升
- 第一个语句是声明语句,在编译阶段处理。// var a
- 第二个语句是赋值语句,在运行阶段处理。// a = 2
那么我们再回过头来看看JS脚本执行顺序问题中出现的代码:
1 | a = 2; |
应该这样来处理:1
2
3var a; //编译阶段
a = 2; //运行阶段
console.log(a); //运行阶段
第二段代码:1
2console.log(a);
var a = 2;
应该这样来处理:1
2
3var a; //编译阶段
console.log(a); //运行阶段
a = 2; //运行阶段
所以这段代码的最终输出结果是undefined。
变量提升需要注意两点:
- 提升的部分只是变量声明,赋值语句和可执行的代码逻辑还保持在原地不动
- 提升只是将变量声明提升到变量所在的变量范围的顶端,并不是提升到全局范围,说明如下:
1 | foo(); |
函数声明会提升,但是函数表达式就不了。
看如下代码:1
2
3
4
5
6foo();
var foo = function bar(){ //这是一个函数表达式,不再是函数声明。
console.log("bar");
}
//以上代码会报错:
Uncaught TypeError: foo is not a function
代码实质执行方式如下:1
2
3
4
5
6var foo;
foo(); //TypeError,因为还没有赋值
bar(); //bar不可以在全局范围内引用
foo = function bar(){
console.log("bar");
}
函数是一等公民
变量声明和函数声明都会得到变量提升,但函数声明会最先得到提升,然后是变量声明。
考虑如下代码:1
2
3
4
5
6
7
8foo(); //输出的结果为1
var foo;
function foo(){
console.log(1);
}
foo = function(){
console.log(2);
}
代码实质执行方式如下:1
2
3
4
5
6
7function foo(){
console.log(1);
}
foo();
foo = function(){
console.log(2);
}
注意:var foo;由于是重复声明变量,所以被编译优化去掉。
对于函数声明来说,如果定义了相同的函数变量声明,后定义的声明会覆盖掉先前的声明,看如下代码:1
2
3
4
5
6
7
8
9
10foo(); //输出3
function foo(){
console.log(1);
}
var foo = function(){
console.log(2);
}
function foo(){
console.log(3);
}
JavaScript中是没有块级作用域的概念(ps:ES6中有改进了),看如下代码:1
2
3
4
5
6
7
8
9
10
11foo(); //输出结果为2
var a = true;
if(a){
function foo(){
console.log(1);
}
}else{
function foo(){
console.log(2);
}
}
这段代码输出结果为2,if语句没有块级作用域的功能,所以函数声明都被提升到全局作用域中,
又因为定义了两个foo,后来的定义覆盖了前边的定义,所以输出结果为2。
面试题解析
选择了网上三题‘较’难,迷惑的人较多的面试题:
题1:
1 | console.log(foo); // ? |
答案 : undefined ,function bar(){…}
解析:1
2
3
4
5
6
7var foo ;
function bar()
console.log(foo); // undefined
console.log(bar); // function bar()
foo = function() ;
本题主要考的是函数和函数表达式的区别。
变量声明和函数先提升到顶部,赋值被留到原地,foo默认undefined。bar输出函数自己。
题2:
1 | function foo(){ |
答案 :undefined ,5 ,10
解析:
下面是本题的详细解析,考点就是提升和全局污染1
2
3
4
5
6
7
8
9
10
11function foo(){
var a ; // 因为下面有声明a变量,a的声明提前
a = 5; //因为在自己的作用域内有a的声明存在,a并不会污染到全局,
//而是绑定到本作用域的a上,这也是比较忽悠人的地方
console.log(window.a); // undefined
//a = 5 没有污染全局,所以window.a不存在,故输出undefined
console.log(a); // 5 ,a 的声明提升,变量 a = 10 没有提升,a 现在还是 5
a = 10;
console.log(a); // 10
}
foo();
这里涉及到全局污染问题,即不使用 var 或 其他声明关键字 去声明时,在本作用域找不到声明时,
默认向上级找,直到最顶层绑定到全局window上(严格模式报 not defined )。例如:1
2
3
4
5function foo(){
a = 1;
console.log(window.a); // 1 不使用 var定义变量a污染到了全局上
}
foo();
题3:
1 | function foo() { |
答案 :1
解析:
考点 1.污染 2.提升 3.作用域1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function foo() {
var a ; // a 和 b 一起提升到作用域顶部
function b() {
function a() {...} //b里的函数a也提升到b的顶部
a = 10; //因为上面有变量a,所以a也不会污染到上一层,而是对函数a进行再次赋值
//如果函数执行,函数里的a的值是 10,且没有污染
return '';
}
a = 1; //对本作用域的a赋值
b(); //函数执行,b作用域内的a被赋值为10
console.log(a); // 1
// 这个有两点要搞清楚 :
// 1. b的a没有污染到这个作用域
// 2. 就近原则,本函数的log(a)找离自己最近的a变量,
//如果log在函数b内,那么输出 离自己最近的 10
}
foo();