在JavaScript中使用变量或常量前,必须先声明它。在ES6及之后的版本中,这是通过 let 和 const 关键字来完成的,接下来我们会介绍。在ES6之前,变量是通过 var 声明的,这个关键字更特殊一些,将在后面介绍。
使用let和const声明
在现代JavaScript(ES6及之后)中,变量是通过 let 关键字声明的,声明变量的同时(如果可能)也为其赋予一个初始值是个好的编程习惯:
let message="hello"; let i=0,j=0,k=0; //也可以使用一条 let 语句声明多个变量 let x=2,y=x*x; //初始化语句可以使用前面声明的变量 let n; console.log(n);
如果在 let 语句中不为变量指定初始值,变量也会被声明,但在被赋值之前它的值是 undefined。
要声明常量而非变量,则要使用const而非let。const与let类似,区别在于const必须在声明时初始化常量:
const H0=74; //哈勃常数(km/s/Mpc) const C=299792.458; //真空中的光速(km/s) const AU=1.496E8; //天文单位:地球与太阳间的平均距离(km)
顾名思义,常量的值是不能改变的,尝试给常量重新赋值会抛出TypeError。声明常量的一个常见(但并非普遍性)的约定是全部字母大写,如H0或HTTP_NOT_FOUND,以区别于变量。
何时使用const
关于使用const关键字有两种论调。一种论调是只在值基本不会改变的情况下使用const,比如物理常数、程序版本号,或用于标识文件类型的字节序。另一种论调认为程序中很多所谓的变量实际上在程序运行时并不会改变。为此,应该全部使用const声明,然后如果发现确实需要允许值改变,再改成let。这样有助于避免因为意外修改变量而导致出现bug。在第一种情况下,我们只对那些必须不变的值使用const。另一种情况下,对任何不会变化的值使用const。我本人在写代码时倾向前一种思路。
在第5章中,我们会学习JavaScript中的for、for/in和for/of循环语句。其中每种循环都包含一个循环变量,在循环的每次迭代中都会取得一个新值。JavaScript允许在循环语法中声明这个循环变量,这也是let另一个常见的使用场景:
for(let i=0, len=data.length; i < len; i++) console.log(data[i]); for(let datum of data) console.log(datum); for(let property in object) console.log(property);
虽然看起来有点怪,但也可以使用const声明for/in和for/of中的这些循环“变量”,只要保证在循环体内不给它重新赋值即可。此时,const声明的只是一次循环迭代期间的常量值:
for(const datum of data) console.log(datum); for(const property in object) console.log(property);
变量与常量作用域
变量的作用域(scope)是程序源代码中一个区域,在这个区域内变量有定义。通过let和const声明的变量和常量具有块作用域。这意味着它们只在let和const语句所在的代码块中有定义。JavaScript类和函数的函数体是代码块,if/else语句的语句体、while和for循环的循环体都是代码块。粗略地讲,如果变量或常量声明在一对花括号中,那这对花括号就限定了该变量或常量有定义的代码区域(当然,在声明变量或常量的let或const语句之前的代码行中引用这些变量或常量也是不合法的)。作为for、for/in或for/of循环的一部分声明的变量和常量,以循环体作为它们的作用域,即使它们实际上位于花括号外部。
如果声明位于顶级,在任何代码块外部,则称其为全局变量或常量,具有全局作用域。在Node和客户端JavaScript模块中,全局变量的作用域是定义它们的文件。但在传统客户端JavaScript中,全局变量的作用域是定义它们的HTML文档。换句话说,如果有<script>标签声明了一个全局变量或常量,则该变量或常量在同一个文档的任何<script>元素中(或者至少在let和const语句执行之后执行的所有脚本中)都有定义。
重复声明
在同一个作用域中使用多个let或const声明同一个名字是语法错误。在嵌套作用域中声明同名变量是合法的(尽管实践中最好不要这么做):
const x=1; //声明x为全局常量 if(x===1){ let x=2; //在同一个代码块中 console.log(x); //打印2 } console.log(x); //打印1,现在又回到了全局作用域 let x=3; //错误!重新声明x会导致语法错误
声明与类型
如果你使用过静态类型语言(如C或Java),可能认为变量声明的主要目的是为变量指定可以赋予它的值的类型。但我们也看到了,JavaScript的变量声明与值的类型无关。JavaScript变量可以保存任何类型的值。例如,在JavaScript中,给一个变量赋一个数值,然后再给它赋一个字符串是合法的。
使用var的变量声明
在ES6之前的JavaScript中,声明变量的唯一方式是使用var关键字,无法声明常量。var的语法与let的语法相同:
var x; var data=[], count=data.length; for(var i=0; 1 < count; i++) console.log(data[i]);
虽然var和let有相同的语法,但它们也有重要的区别。
使用未声明的变量
在严格模式下,如果试图使用未声明的变量,那代码运行时会触发引用错误。但在严格模式外部,如果将一个值赋给尚未使用let、const或var声明的名字,则会创建一个新全局变量。而且,无论这个赋值语句在函数或代码块中被嵌套了多少次,都会创建一个全局变量。这肯定不是我们想要的,非常容易招致缺陷,也是推荐使用严格模式一个最好的理由。
以这种意外方式创建的全局变量类似使用var声明的全局变量,都定义全局对象的属性。但与通过恰当的var声明定义的属性不同,这些属性可以通过delete操作删除。