先说说var和函数声明
变量提升
变量提升应该是var最引人瞩目的一个特点了。不说废话,先上代码。
1 //code1: 2 a = 10 3 var a 4 console.log(a)//10 5 6 //code2: 7 var a 8 a = 10 9 console.log(a)//10
聪明的你肯定都猜对了输出结果。其实代码1和代码2是等价的。为什么呢?关键就是变量提升(hoisting)。对于用var声明的变量,其声明都会被提升到作用域顶部。
来分析一下代码1:
- 浏览器预编译阶段,浏览器找到声明语句var a,创建变量a,初始化undefined。
- 浏览器执行阶段,a = 10给a赋值10,然后打印。
所以,变量提升其实就是声明语句被提升到作用域的顶部执行的一种现象。
ps:但如果有深入了解js运行机制的人肯定对这种说法是不解的。其实,所谓变量提升不过是从表面上简化地解释了js的运行机制。实质上,在预编译阶段阶段,浏览器会首先找到所有的变量和函数声明,把变量初始化值为undefined(此处暂不考虑let和const声明),函数声明整体提升(下面会讲到)。然后浏览器再开始执行代码,比如赋值,运算等等。所以,看起来就像变量的声明被提升到作用域顶部执行了一样。不过所谓变量提升也并没有标准的定义,我所说的定义只是当下一种普遍的理解。不过对初学者来说,用变量提升来理解var确实可能是一中比较好的做法,上来就提运行机制什么的,那会吓跑很多JS的潜在爱好者吧。后面有机会的话也还会讲讲JS的运行机制哦。
特别注意,函数作用域中用var声明的变量在全局作用域无法访问到。例如:
console.log(a) function c (){ var a console.log(b) function d() { var b } }
这里代码会报错,因为a只在函数c作用域声明,b只在函数d作用域有声明。当使用的变量声明不在当前作用域中,引擎会到上一级作用域去寻找,如果找不到,就会报错,并不会去下一级别作用域中寻找。
那不用var声明会怎么样?
先看代码:
//code:1 console.log(a) // Uncaught ReferenceError: a is not defined a = 3 //code:2 a = 3 console.log(a)//3
代码1中编译器会报错,而2则会正常打印3。在1中a并没有声明,所以不会有变量提升,所以打印a时显示未定义。2中编译器预编译时没找到任何变量声明,执行时遇到未声明的变量a,把其初始化为全局变量,再进行赋值,表面上看起来与var a = 3并无什么区别,但本质上还是不一样的。
不如再来聊聊函数声明和var的联系
先上代码
alert(a); // 弹出function a(){alert(2)} function a(){ alert(2); } function a(){ alert(2); }
和var声明的变量一样,函数声明也存在提升现象。也许你发现了不同:函数声明是整体提升的(alert中弹出的并不是undefined,而是整个函数整体),而var声明的变量只是声明提升初始化为undefined。
接下来再看一段代码:
foo();
function foo() { console.log('foo'); } var foo = 2;
屏幕前的你是否感到有点懵逼?what?两者都有变量提升,那foo最后是函数还是变量?
答案是,还是函数,并且这段代码会打印出foo。为什么呢?如果现阶段你还不想深究,那你可以暂时认为,函数声明提升优于变量提升。
那如果在var foo = 2后打印foo,是函数还是2呢?答案是2。
事实上,在浏览器内部对代码是这么处理的:
- 预编译阶段,找到函数声明和变量声明,为其开辟内存空间。
- 再从头开始执行代码,比如函数调用和赋值等等操作。
在这段代码里面,浏览器在预编译阶段找到了函数foo的声明,那么会为foo开辟内存空间,然后对函数内部的声明等等还会有一些操作。然后找到了变量foo的声明,但由于foo已经存在内存空间中,所以不再为其开辟内存空间和初始化。
然后开始执行代码,先调用函数,打印出了foo。接下来为foo赋值2,这时候foo由function变成了number。这时候如果再打印foo,就是2了。
很多人会问,如果代码是下面这样,结果会怎么样:
1 foo(); 2 3 var foo = 2; 4 5 function foo() { 6 console.log('foo'); 7 }
可能我会告诉你,这段代码和上面那段代码是等价的。并且如果你在最下面再添一行代码,打印foo,结果仍是2。为什么呢?因为函数声明是整体提升的,即便他的声明在变量后,他的声明依然会覆盖上面变量的声明,然而反过来变量声明是无法覆盖函数声明的。如果对于初学者,从表面上看,那最好的理解就是认为函数声明比变量声明优先级高。注意,我说的只是声明哦。
注意
下面这种情况属于函数表达式,函数不会提升
1 a() //报错: a is not a function 2 3 var a = function(){ 4 console.log('123') 5 }
1 console.log(a) //undefined 2 3 var a = function(){ 4 console.log('123') 5 }
var lock = true a() //报错:a is not a function if (lock) { function a(){ console.log('1') } }else{ function a(){ console.log('1') } }
开小灶啦
对我上面讲的可能很多观众还是无法理解的,好学的朋友建议再去搜索一些JS运行机制相关文章进行学习。
上面说到函数声明提升时浏览器内部还会对函数进行一些操作,具体可以参考https://zhuanlan.zhihu.com/p/36373165