写在前面的话
⭐ 首先在最开始介绍一下,JS中存在提升的原因?
JS引擎在读取代码时,进行两步操作:
- 第一步:对整个JS代码进行读取解析
- 第二步:代码执行
提升只会发生在第一步中,所以在代码执行之前,浏览器的解析器遇到变量名和函数声明时,会将其提升至当前作用域的最前面。
注意:
- 上述提及的变量提升,只针对
var
声明的变量,使用let
或const
声明的变量不存在提升。 - JS引擎严格规定句首用
function
的才能算作函数声明,其余的杂牌声明只能算作函数表达式.
变量提升 & 函数提升
- 变量提升上述代码相当于:
1
2
3console.log(a);//undefined
var a = 'hi';
console.log(a);//'hi'1
2
3
4var a; // 只定义不声明就默认是undefined
console.log(a);//undefined
a='hi';
console.log(a);//hi - 函数提升上述代码相当于:
1
2
3
4
5
6
7
8
9console.log(a); //f a(){console.log('hi');}
console.log(b); //undefined
function a() {
console.log('hi');
}
var b = function() {
console.log('ok');
}1
2
3
4
5
6
7
8
9function a () {
console.log('hi');
}
var b;//undefined;
console.log(a); //f a(){console.log('hi');}
console.log(b); //undefined
b = function () {
console.log('ok');
}
二者的优先级
- 函数提升会优先于变量提升,而且不会被同名的变量覆盖。
- 但是,如果这个同名变量被赋值了,那函数变量就会被覆盖。
- 当二者同时存在时,会先指向函数声明。上述代码相当于:
1
2
3
4
5
6
7
8
9console.log(a); //f a() {...}
console.log(a()); //2
var a = 1;
function a() {
console.log(2);
}
console.log(a); //1
a = 3;
console.log(a()); //报错,现在的函数a已经被赋值过后的变量a给覆盖了,无法再调用a()1
2
3
4
5
6
7
8
9
10function a() {
console.log(2);
}
var a;
console.log(a); //f a() {...}
console.log(a()); //2
a = 1;
console.log(a); //1
a = 3;
console.log(a()); //报错,现在的函数a已经被赋值过后的变量a给覆盖了,无法再调用a()
牛客网上的一道题目:
以下代码执行之后,控制台的输出是:
1 | var a = 10; |
答案是:Number。
原因是函数提升优先级高于变量提升,先执行函数提升,再执行变量提升。执行完函数提升之后,又执行了变量提升并进行赋值操作。此时函数变量被覆盖。
1 | var a; |
答案是:function。
此时先执行了函数提升,又执行了变量提升,但是变量没有进行赋值,因此指向函数。【当二者同时存在时,会先指向函数声明】
为什么会有提升?
Dmitry Soshnikov早些年在twitter上提出关于变量提升的问题,同时Brendan Eich给出了一些解答:
答案的大致意思是由于第一代JS虚拟机中的抽象纰漏导致的,编译器将变量放入栈槽内并编入索引。然后在(当前作用域)入口处将变量名绑定到栈槽内的变量上。(注:这里提到的抽象是计算机术语,是对内部发生的更加复杂的事情的一种简化。)
随后,Dmitry Soshnikov又提到了函数提升,他提到了函数的相互递归。
Brendan Eich又对其进行了解答。
大致意思是函数提升就是为了解决函数相互递归问题,大体上可以解决像ML语言这样自下而上的顺序问题。
对于函数递归的一些阐述:
1 | // 验证偶数 |
对于中间语句console.log(isEven(2));
而言,它在执行的时候,isOdd
函数还没有被声明,如果没有函数提升,那就必然会报错,这段程序就会陷入一种死循环的状态。
因此,在JS设计之初,就人为加入了函数提升。在代码执行时,将函数提升至当前作用域的顶部。
1 | // 验证偶数 |
最后,Brendan Eich还对变量提升和函数提升做了总结:
大概是说,变量提升是人为实现的问题,而函数提升在当初设计时是有目的的。
后续的补充
在stackoverflow中用户提出一道相关问题Javascript function scoping and hoisting
大意就是他阅读了Ben Cherry写的一篇关于JS范围和提升的文章,对其中给出的一个案例存在疑惑,为什么代码执行结果是浏览器输出1?
然后下面有人对其问题进行了解答。
函数提升意味着函数被移动到作用域的开头。(红框中的代码的执行过程实际上是绿框中代码的顺序)