我们在写js函数的时候,一般情况下,会避免变量重名,以及变量和函数重名的情况,所以很少会涉及到变量提升的概念。
变量提升都是发生在js预编译过程中的,能够完全理解变量提升,有利于我们彻底弄懂js的概念。
var a = 1; function test(a) { console.log(a) var a = 12; function a() {} console.log(a) } test(34)
在我们的面试过程中,如果存在笔试,那么少不了类似于这种题目。那么这么倒题目,其中两个console.log(a)究竟打印出来的是什么呢?看结果:
那我们怎么理解这种提升,以及这些提升的规则又是什么呢?
在预编译过程中,我们可以这么理解:
系统首先创建一个对象,假设为[[AO]],然后查找形参和声明变量,作为该对象的属性,其值为undefined;然后将形参的值和实参的值统一,也就是把[[AO]]对象当中属性为形参的值赋值,如果传递了实参,那么这时候修改属性值为实际参数值;最后在函数体内查找函数声明,值为函数体。
在上述例子中的第一个console.log(a)的位置,如果函数体内没有函数声明function a (){},那么这里打印出来的值为实际参数值34。
当预编译结束之后,各种变量赋值也好,函数赋值也好,都是重新修改该对象内属性的值,运行到哪一步,就在哪一步修改。
确认一点,变量提升,只适用于var定义的变量。
如果使用let或者const定义变量,那么就不存在上述各种规则。将上述代码稍作修改:
var a = 1; function test(a) { console.log(a) let a = 12; function a() {} console.log(a) } test(34)
唯一变动,就是在函数体内部声明变量时,使用let声明替换了之前的var声明,如果此时再次运行的话,就会发现:报错了!
提示为请确认a是否已经声明。这是因为函数体内使用了let声明变量,但是在声明之前就开始调用了该变量,导致报错。
在阮一峰老师的书《ECMAScript 6 入门》中说到:
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
这就是所谓的暂时性死区。在暂时性死区内,不仅无法提前使用let声明的变量,他也不会获取函数体外部作用域内的同名变量。这就是我们例子中所展示的即使外部和形参名称均为a,我们在函数体内部也不能先使用a变量。暂时性死区会到该变量被声明后结束。
如果希望了解更多,请参考阮一峰老师所述的“暂时性死区”。
那就可以总结一下:
1、只有var定义的变量存在变量提升这一说法,而且必然会发生变量提升。
2、变量提升和函数提升的过程:
1)先找var定义的变量和形参: a--> undefined
2)实参和形参统一
3)函数声明提升(仅仅函数声明,函数表达式是不存在提升一说的)
3、如果块级作用域内出现let或者const定义的变量,而且在该变量定义之前使用了,那么必然报错。