本文主要聊下JavaScript中的作用域和函数提升,这些知识点比较细,容易忽略,实际编程中也是容易出现bug的地方。
一.作用域
作用域是可访问变量的集合,简单来说就是变量的作用范围。在JavaScript中作用域分为全局作用域和函数作用域。
1.全局作用域
直接在script标签中编写的代码都运行在全局作用域中。
全局作用域在打开页面时创建,在页面关闭时销毁。
全局作用域中有一个全局对象window,window对象由浏览器提供,可以在页面中直接使用,它代表的是整个的浏览器的窗口。
在全局作用域中创建的变量都会作为window对象的属性保存,在全局作用域中创建的函数都会作为window对象的方法保存。
在全局作用域中创建的变量和函数可以在页面的任意位置访问,在函数作用域中也可以访问到全局作用域的变量。
尽量不要在全局中创建变量。
2.函数作用域
函数作用域是函数执行时创建的作用域,每次调用函数都会创建一个新的函数作用域。
函数作用域在函数执行时创建,在函数执行结束时销毁。
在函数作用域中创建的变量,不能在全局中访问。
当在函数作用域中使用一个变量时,它会先在自身作用域中寻找,如果找到了则直接使用,如果没有找到则到上一级作用域中寻找,如果找到了则使用,找不到则继续向上找;若全局作用域也没有,则报错。
1 | function fun() { |
二.声明提升
在JavaScript中,函数与变量的声明可以提升到函数的最顶部。通俗地讲就是先上车后补票,先使用,后声明。
1.变量声明提升
在全局作用域中,使用var关键字声明的变量会在所有的代码执行之前被声明,但是不会赋值。因此下面代码输出”aa = undefined”
1 | <head> |
所以我们可以在变量声明前使用变量。但是不使用var关键字声明的变量不会被声明提前。正如下面将script中的代码改成这样则报错。
1 | <script> |
在函数作用域中,使用var关键字声明的变量会在函数所有的代码执行前被声明,如果没有使用var关键字声明变量,则变量会变成全局变量。
1 | var c = 99; |
对于上述代码,使用了var关键字声明变量,声明提前,相当于在“console.log(“c = “ + c);”之前执行“var c”,并未赋值,所以输出“c = undefined”
下面换一种方式:
1 | var c = 99; |
对于上面这段代码,与前面不同fun3中的变量c没有用var声明。
从代码逻辑来看首先执行fun3函数,当要执行①句时发现前面没有出现变量c,于是从上一级寻找,找到了c=99,最后输出c = 99;轮到执行②句时,由于fun3中的c没有var修饰,因此是全局变量,②句在全局输出,所以此时c的值为33。
2.函数声明提升
在全局作用域中,使用函数声明创建的函数(function fun(){}),会在所有的代码执行之前被创建,也就是我们可以在函数声明前去调用函数,但是使用函数表达式(var fun = function(){})创建的函数没有该特性
1 | <head> |
函数声明会连通命名和函数体一起被提升至作用域顶部,对于以上代码毫无疑问输出的是“我是一个fun函数”, 但是若改成下面这种情况就报错。
原因是下面这个例子被提升的仅仅是变量名fun2,至于它的定义依然停留在原处。因此在执行fun2()之前,作用域只知道fun2的命名,不知道它到底是什么,所以执行会报错(通常会是:undefined is not a function)。这叫做函数表达式(Function Expression),函数表达式只有命名会被提升,定义的函数体则不会。
1 | <script> |
在函数作用域中,使用函数声明创建的函数,会在所有的函数中的代码执行之前就被创建好了。
总结
函数声明和变量声明总是会被解释器悄悄地被”提升”到方法体的最顶部。两者区别不大,两者的生命周期略有差异,都取决于它们处于函数作用域还是全局作用域。