本文主要聊下JavaScript中的作用域和函数提升,这些知识点比较细,容易忽略,实际编程中也是容易出现bug的地方。

一.作用域

作用域是可访问变量的集合,简单来说就是变量的作用范围。在JavaScript中作用域分为全局作用域和函数作用域。

1.全局作用域

直接在script标签中编写的代码都运行在全局作用域中。

全局作用域在打开页面时创建,在页面关闭时销毁。

全局作用域中有一个全局对象window,window对象由浏览器提供,可以在页面中直接使用,它代表的是整个的浏览器的窗口。
image

在全局作用域中创建的变量都会作为window对象的属性保存,在全局作用域中创建的函数都会作为window对象的方法保存。

在全局作用域中创建的变量和函数可以在页面的任意位置访问,在函数作用域中也可以访问到全局作用域的变量。

尽量不要在全局中创建变量。

2.函数作用域

函数作用域是函数执行时创建的作用域,每次调用函数都会创建一个新的函数作用域。

函数作用域在函数执行时创建,在函数执行结束时销毁。

在函数作用域中创建的变量,不能在全局中访问。

当在函数作用域中使用一个变量时,它会先在自身作用域中寻找,如果找到了则直接使用,如果没有找到则到上一级作用域中寻找,如果找到了则使用,找不到则继续向上找;若全局作用域也没有,则报错。

1
2
3
4
5
6
7
8
9
10
function fun() {
var ab = "safsa123";

function fun2() {
var ab = "fs";
console.log("ab = " + ab);
}
fun2(); //输出 ab = fs
}
fun(); //输出 ab = fs

7hPdaQ.png

二.声明提升

在JavaScript中,函数与变量的声明可以提升到函数的最顶部。通俗地讲就是先上车后补票,先使用,后声明。

1.变量声明提升

在全局作用域中,使用var关键字声明的变量会在所有的代码执行之前被声明,但是不会赋值。因此下面代码输出”aa = undefined”

1
2
3
4
5
6
7
8
9
10
11
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript_function</title>

<script>
console.log("aa = " + aa); //结果:aa = undefined
var aa = 6;
</script>

</head>

所以我们可以在变量声明前使用变量。但是不使用var关键字声明的变量不会被声明提前。正如下面将script中的代码改成这样则报错。

1
2
3
4
<script>
console.log("aa = " + aa); //Uncaught ReferenceError: aa is not defined
aa = 6;
</script>

在函数作用域中,使用var关键字声明的变量会在函数所有的代码执行前被声明,如果没有使用var关键字声明变量,则变量会变成全局变量。

1
2
3
4
5
6
7
8
var c = 99;

function fun3() {
console.log("c = " + c);
var c = 33;
}
fun3();
//结果输出 c = undefined

对于上述代码,使用了var关键字声明变量,声明提前,相当于在“console.log(“c = “ + c);”之前执行“var c”,并未赋值,所以输出“c = undefined”

下面换一种方式:

1
2
3
4
5
6
7
8
9
10
var c = 99;

function fun3() {
console.log("c = " + c); //-------①
c = 33; //无声明提升
// var c = 33;
}
fun3(); //输出 c = 99
console.log("全局输出c = " + c); //------②
//不用var则为全局变量,输出“c = 33”

对于上面这段代码,与前面不同fun3中的变量c没有用var声明。

从代码逻辑来看首先执行fun3函数,当要执行①句时发现前面没有出现变量c,于是从上一级寻找,找到了c=99,最后输出c = 99;轮到执行②句时,由于fun3中的c没有var修饰,因此是全局变量,②句在全局输出,所以此时c的值为33。

2.函数声明提升

在全局作用域中,使用函数声明创建的函数(function fun(){}),会在所有的代码执行之前被创建,也就是我们可以在函数声明前去调用函数,但是使用函数表达式(var fun = function(){})创建的函数没有该特性

1
2
3
4
5
6
7
8
9
10
11
12
13
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible"
<title>Document</title>

<script>
fun();

function fun(){
console.log("我是一个fun函数");
}
</script>
</head>

函数声明会连通命名和函数体一起被提升至作用域顶部,对于以上代码毫无疑问输出的是“我是一个fun函数”, 但是若改成下面这种情况就报错。

原因是下面这个例子被提升的仅仅是变量名fun2,至于它的定义依然停留在原处。因此在执行fun2()之前,作用域只知道fun2的命名,不知道它到底是什么,所以执行会报错(通常会是:undefined is not a function)。这叫做函数表达式(Function Expression),函数表达式只有命名会被提升,定义的函数体则不会。

1
2
3
4
5
6
7
<script>
fun2();

var fun2 = function(){
console.log("我是一个fun2函数"); //报错
}
</script>

在函数作用域中,使用函数声明创建的函数,会在所有的函数中的代码执行之前就被创建好了。

总结

函数声明和变量声明总是会被解释器悄悄地被”提升”到方法体的最顶部。两者区别不大,两者的生命周期略有差异,都取决于它们处于函数作用域还是全局作用域。

评论