剑指JavaScript:核心原理与应用实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.1 var声明

在JavaScript程序中,使用变量之前需先声明,ES6之前的版本通过关键字var定义变量,比如:

上述代码中定义了一个名为name的变量,可以用它存储JavaScript支持的任意类型值。比如:

这段代码在变量name中存入值atguigu。

事实上,使用var定义变量有三种方式,分别为“先声明,后赋值”、“在声明的同时进行赋值”和“一次声明多个变量”。上面这段代码使用的定义方式是“先声明,后赋值”,本质是先声明变量,再对变量进行赋值。

“在声明的同时进行赋值”与“先声明,后赋值”的本质是一样的,只是它将变量声明和赋值写在一起,请看下面的代码:

运行代码,控制台输出字符串atguigu,与“先声明,后赋值”方式产生的效果相同。

“一次声明多个变量”也是在开发中常用的一种变量声明方式,通常有两种使用情况,分别是声明多个变量但不赋值和声明多个变量并分别赋值。下面将分别对这两种情况进行演示。

声明多个变量但不赋值是通过一个var关键字对多个变量进行声明,变量间使用逗号“,”分隔。比如:

代码运行后,控制台输出f=30、g=30。

声明多个变量并分别赋值是通过一个var关键字为多个变量声明并赋值,变量间使用逗号“,”分隔。比如:

代码运行后,控制台输出d=10、e=20。

上面的例子都是将变量的定义和赋初值一起完成,代码如下:

或者将它们拆分为两条语句:

你可能会有疑问:如果一个变量仅被var定义出来,但没有用等号赋值,它的值是什么呢?

比如:

代码运行后,控制台输出undefined。undefined的意思为“不明确的,未被定义的”,是JavaScript中的一个特殊值。当一个变量仅被var定义,但是没有被赋值时,它的默认值是undefined。

其实不通过关键字var也可以声明一个变量,但是我们并不推荐使用这种方式。比如:

这段代码在普通模式下是可以正常运行的,当“console.log(c);”查找变量c时,JS引擎会在全局搜索变量c。但因为变量c没有使用var关键字声明,所以JS引擎会定义全局变量c。代码运行后,控制台会输出300。

而在严格模式下JS引擎不会自动创建变量,在查找变量c的时候,变量c并不存在,会抛出ReferenceError。也就是说,相同的代码在不同模式下的返回结果是完全不同的。比如:

注意:

对于带var声明的变量和不带var声明的变量,目前可以将二者理解为作用相同。但是它们是有区别的,在普通模式下,如果一个变量没有声明就赋值,默认是全局变量。但在严格模式下,这种写法是被禁止的,如果给一个没有声明的变量赋值,那么代码在执行时就会抛出ReferenceError。而带var声明的变量,不管是在普通模式下还是在严格模式下,都被认为是全局变量。在以后声明变量的时候,我们推荐不要省略var。

2.1.1 var声明作用域

在JavaScript中,作用域为可访问变量的集合。所谓作用域,就是变量起作用的区域(也称为范围)。也就是说,作用域控制变量的可访问区域。

使用var声明变量的有效范围是什么呢?其实这取决于定义变量的位置,在ES5中,可以在函数内和函数外定义变量。当在函数体大括号内(也叫函数内)定义变量时,该变量的有效范围就在函数内,也就是函数作用域。比如:

上述代码定义了函数scope(),并在函数内定义了变量b。此时变量b的有效范围为函数作用域,故输出2。这就好比北京市和北京一卡通的关系,北京一卡通只能在北京市使用,一旦离开了北京市,北京一卡通就不能使用。变量b就相当于北京一卡通,函数scope()就相当于北京市。记住:在大括号内定义的变量的有效范围都是函数作用域。

当在函数外定义变量时,也就是在函数体大括号外(也叫函数外)定义变量时,变量的有效范围就在整个<script></script>标签对中,在JavaScript中将这个范围叫作全局作用域。比如:

上述代码在函数内定义了变量b,在函数外定义了变量a,根据作用域的概念,它们分别属于函数作用域和全局作用域,故输出1和2。在这里,可以将变量a比作信用卡,全局作用域比作全世界,变量b和函数scope()依旧比作北京一卡通和北京市。我们都知道信用卡在世界各地都可以使用,因此它就可以是全世界的作用域。也就是说,变量a在全局作用域中可以随便使用。而北京一卡通只能在北京市使用,变量b只能在函数scope()的作用域中使用,在函数外,也就是全局作用域中是不能使用的。

ES5是以函数体大括号来界定函数作用域和全局作用域的,当使用var在函数体大括号内定义变量时,变量的作用域就只能在函数内使用,有效范围是函数作用域;当使用var在函数体大括号外定义变量时,在整个<script></script>标签对内都可以使用这个变量,有效范围是全局作用域。需要特别注意的是,如果变量是在函数内定义的,那么在函数外无法读取该变量,甚至会出现报错现象。比如:

上述代码定义了一个名叫scope()的函数,在函数内使用var定义了id并赋值为1,此时在函数内是可以读取id的值的,且在函数外是读取不到id的值的。因此会出现ReferenceError:id is not defined,如图2-1所示。

图2-1 var作用域图解

值得一提的是,当使用var在全局作用域内声明变量时,该变量会成为window对象的属性(window对象在第10章介绍),可以通过下方代码进行验证:

2.1.2 var声明提升

在使用var定义变量的时候,声明的变量会被提升到作用域的最前面,变量的赋值不会被提升。比如:

上述代码中的变量b被提升,代码等价为:

此时代码结构已经非常清晰了,我们来逐行分析上面的代码。首先看第一行代码,由于变量b的声明被提升,没有对变量b赋值,因此第二行代码的输出结果为undefined,第三行代码为变量b赋值0,此时输出变量b的值为0,如图2-2所示。

图2-2 var声明提升图解

其实var声明变量有两种情况,一种情况是在全局作用域中声明,另一种情况是在函数作用域中声明(函数作用域在第6章会进行相关介绍)。需要注意的是,不管是在全局作用域中声明变量,还是在函数作用域中声明变量,变量只会被提升到当前作用域的最前面。比如:

这段代码的变量提升与全局作用域中的提升类似,通过声明提升,将变量b提升至函数作用域的最前面,如图2-3所示。

图2-3 函数内var声明提升图解

下面的代码在全局作用域中定义了一个函数,在全局中和函数内分别定义了变量。在进行解析的时候,只会将变量提升至当前作用域的最前面。代码如下:

这段代码定义了一个名为scope()的函数,并在全局中进行调用。在全局作用域中,变量a被提升至全局作用域的最前面;在函数作用域中,变量b被提升至函数作用域的最前面。上面的代码等价于:

在这段代码中,变量a定义在全局中,因此被提升至全局作用域的最前面。变量b定义在函数scope()内,因为关键字var的声明范围是函数作用域,不能提升至全局作用域的最前面,只能提升至当前函数作用域的最前面,所以在函数作用域外部是读取不到变量b的,如图2-4所示。

图2-4 变量提升图解

var可以重复声明一个变量,此时会先提升变量,后续的重复声明都担任着“赋值”的角色,比如:

上述代码等同于下方代码: