- 剑指JavaScript:核心原理与应用实践
- 尚硅谷教育编著
- 2626字
- 2024-05-11 19:05:56
2.1 var声明
在JavaScript程序中,使用变量之前需先声明,ES6之前的版本通过关键字var定义变量,比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_27_1.jpg?sign=1739472046-s88napnKCqf34rMfmSGbnbrBblQuD0oY-0-e294846f0362ee2b43d9aaca03119888)
上述代码中定义了一个名为name的变量,可以用它存储JavaScript支持的任意类型值。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_27_2.jpg?sign=1739472046-zgxOeF9EYaPjWUczG8tpHq7maFDToOrw-0-2db2e0e2eb82f3a244759ed104e4ae6f)
这段代码在变量name中存入值atguigu。
事实上,使用var定义变量有三种方式,分别为“先声明,后赋值”、“在声明的同时进行赋值”和“一次声明多个变量”。上面这段代码使用的定义方式是“先声明,后赋值”,本质是先声明变量,再对变量进行赋值。
“在声明的同时进行赋值”与“先声明,后赋值”的本质是一样的,只是它将变量声明和赋值写在一起,请看下面的代码:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_27_3.jpg?sign=1739472046-EL7mGj29uG71QIM3QFc7WiIAxXLXlGWj-0-dc7b3e92c4f05bb088c146b6056e8465)
运行代码,控制台输出字符串atguigu,与“先声明,后赋值”方式产生的效果相同。
“一次声明多个变量”也是在开发中常用的一种变量声明方式,通常有两种使用情况,分别是声明多个变量但不赋值和声明多个变量并分别赋值。下面将分别对这两种情况进行演示。
声明多个变量但不赋值是通过一个var关键字对多个变量进行声明,变量间使用逗号“,”分隔。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_28_1.jpg?sign=1739472046-mfgbg85a0PhMWS7bKTLzDkbtNfHA4ZRM-0-50f821a4a4c8b40c9bbd781ecbd539fe)
代码运行后,控制台输出f=30、g=30。
声明多个变量并分别赋值是通过一个var关键字为多个变量声明并赋值,变量间使用逗号“,”分隔。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_28_2.jpg?sign=1739472046-jCXvXghjGGySVG5QRe3VVAHUXtWjbq40-0-3b9c7c028aab3e897c14f5352338fb48)
代码运行后,控制台输出d=10、e=20。
上面的例子都是将变量的定义和赋初值一起完成,代码如下:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_28_3.jpg?sign=1739472046-kOYFVNfYuadLLUq0Zt6VlcvJ2yKKQ5ZM-0-c7e81ccc57db18cbef8dfb4daa3c4767)
或者将它们拆分为两条语句:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_28_4.jpg?sign=1739472046-uXShQjcMDyoGcmRloDMV0fJHgwx5iYcj-0-9683ccfa12048b364c8597f5b353c074)
你可能会有疑问:如果一个变量仅被var定义出来,但没有用等号赋值,它的值是什么呢?
比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_28_5.jpg?sign=1739472046-EY0abBfT0lX0qKRphlcDbCeaNlsJiwm4-0-360578b4054902dfce7b970273b6748c)
代码运行后,控制台输出undefined。undefined的意思为“不明确的,未被定义的”,是JavaScript中的一个特殊值。当一个变量仅被var定义,但是没有被赋值时,它的默认值是undefined。
其实不通过关键字var也可以声明一个变量,但是我们并不推荐使用这种方式。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_28_6.jpg?sign=1739472046-UDqcl32q23LZvMJmDro4YFOkOv052jer-0-b12722eae7cf9b7a3bc61080f56c1115)
这段代码在普通模式下是可以正常运行的,当“console.log(c);”查找变量c时,JS引擎会在全局搜索变量c。但因为变量c没有使用var关键字声明,所以JS引擎会定义全局变量c。代码运行后,控制台会输出300。
而在严格模式下JS引擎不会自动创建变量,在查找变量c的时候,变量c并不存在,会抛出ReferenceError。也就是说,相同的代码在不同模式下的返回结果是完全不同的。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_28_7.jpg?sign=1739472046-iMSd8zyM3Nc2dlDmMqGIz5nriOJmW37I-0-ea44509a1dfd6bbd10f94a33d5507ae9)
注意:
对于带var声明的变量和不带var声明的变量,目前可以将二者理解为作用相同。但是它们是有区别的,在普通模式下,如果一个变量没有声明就赋值,默认是全局变量。但在严格模式下,这种写法是被禁止的,如果给一个没有声明的变量赋值,那么代码在执行时就会抛出ReferenceError。而带var声明的变量,不管是在普通模式下还是在严格模式下,都被认为是全局变量。在以后声明变量的时候,我们推荐不要省略var。
2.1.1 var声明作用域
在JavaScript中,作用域为可访问变量的集合。所谓作用域,就是变量起作用的区域(也称为范围)。也就是说,作用域控制变量的可访问区域。
使用var声明变量的有效范围是什么呢?其实这取决于定义变量的位置,在ES5中,可以在函数内和函数外定义变量。当在函数体大括号内(也叫函数内)定义变量时,该变量的有效范围就在函数内,也就是函数作用域。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_29_1.jpg?sign=1739472046-ULb7YOiVFKWeFiAxYdkyAvjlMjPxDkW4-0-c6026c443a02cf3a3651edbf91559292)
上述代码定义了函数scope(),并在函数内定义了变量b。此时变量b的有效范围为函数作用域,故输出2。这就好比北京市和北京一卡通的关系,北京一卡通只能在北京市使用,一旦离开了北京市,北京一卡通就不能使用。变量b就相当于北京一卡通,函数scope()就相当于北京市。记住:在大括号内定义的变量的有效范围都是函数作用域。
当在函数外定义变量时,也就是在函数体大括号外(也叫函数外)定义变量时,变量的有效范围就在整个<script></script>标签对中,在JavaScript中将这个范围叫作全局作用域。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_29_2.jpg?sign=1739472046-479eSNTxGz7FLtGCS0UlEChEtqE57RVp-0-8ca9f27250d2abf680777fed69ac717d)
上述代码在函数内定义了变量b,在函数外定义了变量a,根据作用域的概念,它们分别属于函数作用域和全局作用域,故输出1和2。在这里,可以将变量a比作信用卡,全局作用域比作全世界,变量b和函数scope()依旧比作北京一卡通和北京市。我们都知道信用卡在世界各地都可以使用,因此它就可以是全世界的作用域。也就是说,变量a在全局作用域中可以随便使用。而北京一卡通只能在北京市使用,变量b只能在函数scope()的作用域中使用,在函数外,也就是全局作用域中是不能使用的。
ES5是以函数体大括号来界定函数作用域和全局作用域的,当使用var在函数体大括号内定义变量时,变量的作用域就只能在函数内使用,有效范围是函数作用域;当使用var在函数体大括号外定义变量时,在整个<script></script>标签对内都可以使用这个变量,有效范围是全局作用域。需要特别注意的是,如果变量是在函数内定义的,那么在函数外无法读取该变量,甚至会出现报错现象。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_29_3.jpg?sign=1739472046-1KLwm4xp136FUDP0nOf7ZbamfP1wXXRj-0-05664681677ba7b55de4e0fc33ddb758)
上述代码定义了一个名叫scope()的函数,在函数内使用var定义了id并赋值为1,此时在函数内是可以读取id的值的,且在函数外是读取不到id的值的。因此会出现ReferenceError:id is not defined,如图2-1所示。
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_30_1.jpg?sign=1739472046-y9vtBMAsX31GOSmh4dImkHeqFmU7qtnq-0-849bf5eb6672c5b3f325f634866a0397)
图2-1 var作用域图解
值得一提的是,当使用var在全局作用域内声明变量时,该变量会成为window对象的属性(window对象在第10章介绍),可以通过下方代码进行验证:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_30_2.jpg?sign=1739472046-Sg2upkUX4XVVZjY7xSBUsCK8uvYxI9NY-0-f4d418a6466ccc1603fbfc109a90873d)
2.1.2 var声明提升
在使用var定义变量的时候,声明的变量会被提升到作用域的最前面,变量的赋值不会被提升。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_30_3.jpg?sign=1739472046-epJKsmkaa07wXnvEOUrZ9EaBA741iwWf-0-b31296b3791edf6c108e377df2ef40ca)
上述代码中的变量b被提升,代码等价为:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_30_4.jpg?sign=1739472046-WyBeH3hhhlDaDS0Eci1uscj7CBBBz5TK-0-8f68bc4e1f9a695bc0c3d928153fbc03)
此时代码结构已经非常清晰了,我们来逐行分析上面的代码。首先看第一行代码,由于变量b的声明被提升,没有对变量b赋值,因此第二行代码的输出结果为undefined,第三行代码为变量b赋值0,此时输出变量b的值为0,如图2-2所示。
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_30_5.jpg?sign=1739472046-LkXIrNV3cinMuoRNvrzcpUX1zBlxpFxx-0-8fdf756f24c7788723a1dde3f738d72d)
图2-2 var声明提升图解
其实var声明变量有两种情况,一种情况是在全局作用域中声明,另一种情况是在函数作用域中声明(函数作用域在第6章会进行相关介绍)。需要注意的是,不管是在全局作用域中声明变量,还是在函数作用域中声明变量,变量只会被提升到当前作用域的最前面。比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_30_6.jpg?sign=1739472046-TnP5mhPiMIE2uJAdhaNjig136gS9VIGd-0-cc66de58c1372cc02f16abf080dde08b)
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_31_1.jpg?sign=1739472046-GrxEOg0TE8dHBPWdSxxdwf4W8dqTd7QC-0-417ef6d45dc691f5f935f61dda7d4493)
这段代码的变量提升与全局作用域中的提升类似,通过声明提升,将变量b提升至函数作用域的最前面,如图2-3所示。
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_31_2.jpg?sign=1739472046-KpCV6zNNByctfZ2UlPZySqVZi6wwdN2f-0-e011568a110b33b2cf02c666524432d6)
图2-3 函数内var声明提升图解
下面的代码在全局作用域中定义了一个函数,在全局中和函数内分别定义了变量。在进行解析的时候,只会将变量提升至当前作用域的最前面。代码如下:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_31_3.jpg?sign=1739472046-ijbCINstZeJ02vj8QAEFEb7ccCi5b24Y-0-e3f801ac3348845a6a3521c14829bd0e)
这段代码定义了一个名为scope()的函数,并在全局中进行调用。在全局作用域中,变量a被提升至全局作用域的最前面;在函数作用域中,变量b被提升至函数作用域的最前面。上面的代码等价于:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_31_4.jpg?sign=1739472046-ZJp2cdnXDwfL6qmjuP2ZfUYSX2bNOrYx-0-0169759d23124854ae74c300a9e03046)
在这段代码中,变量a定义在全局中,因此被提升至全局作用域的最前面。变量b定义在函数scope()内,因为关键字var的声明范围是函数作用域,不能提升至全局作用域的最前面,只能提升至当前函数作用域的最前面,所以在函数作用域外部是读取不到变量b的,如图2-4所示。
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_32_1.jpg?sign=1739472046-pkNIGUPqJVwRlspAjhva8nSoBRxdqxhx-0-babc5304bf6756472afafc95999686f5)
图2-4 变量提升图解
var可以重复声明一个变量,此时会先提升变量,后续的重复声明都担任着“赋值”的角色,比如:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_32_2.jpg?sign=1739472046-GWpV7dlfb4lHGqgY7rHc5olZ6mb6dMd3-0-2d839974b6704b6fc63c88268b6dcbb5)
上述代码等同于下方代码:
![](https://epubservercos.yuewen.com/F694A6/29436049807129906/epubprivate/OEBPS/Images/45235_32_3.jpg?sign=1739472046-xNbcZOEB2V2dX6GtVITUtghmJUTfS36J-0-081ba0a9beb433310821145ecc850c77)