2.5 ECMAScript 6新特新——let、const关键字
本节介绍关于ECMAScript 6语法的新特性,主要就是let和const关键字的使用方法,还包括var关键字与let和const关键字的区别。同时,在介绍ECMAScript 6语法的新特性之前,还会介绍一些关于ECMAScript语法的特殊知识,作为介绍ECMAScript 6语法新特性的铺垫。
2.5.1 变量作用域
前文中,我们介绍不通过“var”关键字来声明变量的方法。其实,使用或不使用“var”关键字还有一个很常见的问题,那就是变量的作用域。
下面来看一个关于ECMAScript变量作用域的代码示例(详见源代码ch02目录中ch02-js-variable-scope.html文件)。
【代码2-36】
01 <script type="text/javascript"> 02 var a = 1; 03 console.log("a = " + a); 04 function func_a() { 05 a = 2; 06 } 07 func_a(); 08 console.log("a = " + a); 09 var b = 1; 10 console.log("b = " + b); 11 function func_b() { 12 var b = 2; 13 } 14 func_b(); 15 console.log("b = " + b); 16 </script>
关于【代码2-36】的分析如下:
第04~06行代码定义一个函数方法(func_a())。其中,第05行代码重新为变量(a)进行赋值操作(a=2);
第07行代码直接调用第04~06行代码定义的函数方法(func_a());
第09~15行代码与第02~08行代码类似,定义变量(b),几乎再次复制第02~08行代码的内容。唯一不同的地方就是第12行代码,细心的读者会发现其与第05行代码的不同,使用“var”关键字重新定义了变量(b)。
页面效果如图2.36所示。
图2.36 ECMAScript变量作用域
从浏览器控制台中输出的内容来看,【代码2-36】中第05行代码没有使用“var”和第12行代码使用“var”关键字定义变量还是有区别的:变量(a)的内容在经过第05行代码的操作后,第08行代码输出重新赋值后的数值;而变量(b)的内容在经过第12行代码的操作后,第15行代码输出的数值没有发生任何改变。这是为什么呢?
因为在第12行代码中,我们使用“var”关键字定义了变量(b),此时的变量(b)仅仅是局部变量(只在函数方法(func_a())中有效)。换句话说,第12行代码定义的变量(b)与第09行代码定义的变量(b)是完全不相关的两个变量,因此第12行代码中对变量(b)的赋值操作(b=2)是根本不会影响到第15行代码的输出结果。
以上就是对变量作用域的简单介绍,接下来介绍ECMAScript语法规范中关于变量提升的知识。
2.5.2 变量提升
在JavaScript(ECMAScript)语法中,变量的提升是一种很常见的现象。那么具体什么是JavaScript(ECMAScript)变量的提升呢?
下面来看一个关于JavaScript(ECMAScript)变量提升的代码示例(详见源代码ch02目录中ch02-js-variable-enhance.html文件)。
【代码2-37】
01 <script type="text/javascript"> 02 console.log("a = " + a); 03 var a = 1; 04 console.log("a = " + a); 05 function func_b() { 06 console.log("b = " + b); 07 var b = 1; 08 console.log("b = " + b); 09 } 10 func_b(); 11 </script>
关于【代码2-37】的分析如下:
第02行和第04行代码分别在定义变量(a)之前和之后,尝试在浏览器控制台窗口中输出变量(a)的内容;
第05~09行代码定义一个函数方法(func_b()),其中第06行和第08行代码分别在定义变量(b)之前和之后,尝试在浏览器控制台窗口中输出变量(b)的内容;
第10行代码调用第05~09行代码定义的函数方法(func_b())。
页面效果如图2.37所示。从浏览器控制台中输出的内容来看,虽然【代码2-37】中定义的第02行代码和第06行代码看似会报错,但实际却输出变量未定义(undefined)的内容。这是为什么呢?
图2.37 ECMAScript变量提升
这是因为JavaScript(ECMAScript)变量提升的特性而产生的结果,在JavaScript(ECMAScript)脚本代码编译过程中,会将全部变量提升到该变量作用域的最顶部,返回到【代码2-37】中,根据变量提升的特点,在执行第02行代码和第06行代码时,变量(a和b)已经存在(只不过未初始化),因此会有输出值(undefined)。
2.5.3 块级作用域
在JavaScript(ECMAScript)语法中,是没有“块级作用域”这个概念的。因此,JavaScript(ECMAScript)全局变量的有效作用域就是整个页面,而局部变量的有效作用域就是其所定义位置的函数内。那么该如何理解呢?
下面来看一个关于JavaScript(ECMAScript)变量“块级作用域”的代码示例(详见源代码ch02目录中ch02-js-variable-block.html文件)。
【代码2-38】
01 <script type="text/javascript"> 02 function func_block() { 03 var i; 04 for (i = 0; i < 3; i++) { 05 console.log("i = " + i); 06 var j = i; 07 } 08 console.log("j = " + j); 09 if (i == 3) { 10 var k = i; 11 } 12 console.log("k = i = " + k); 13 } 14 func_block(); 15 </script>
关于【代码2-38】的分析如下:
第02~13行代码定义一个函数方法(func_block());
第04~07行代码定义一个for循环语句,其自变量就是变量(i)。比较特殊的是第06行代码,通过“var”关键字定义变量(j),并赋值为变量(i)的值;
第09~11行代码通过if条件选择语句判断变量(i)是否等于数值3;
第10行代码通过“var”关键字定义第三个变量(k),并赋值为变量(i)的值;
第14行代码调用了第02~13行代码定义的函数方法(func_block())。
页面效果如图2.38所示。从浏览器控制台中输出的内容来看,虽然【代码2-38】中第06行代码通过“var”关键字定义的第二个变量(j)是在for循环语句内,但第08行代码仍然成功地获取并输出了变量(j)的值。这是为什么呢?
图2.38 ECMAScript块级作用域
这就是因为JavaScript(ECMAScript)语法规范中没有定义“块级作用域”而产生的结果,变量(j)虽然是定义在for循环语句内的,但其有效作用域都是在整个函数方法(func_block())内的。
同样的,第12行代码能够成功获取并输出第10行代码定义的变量(k)的值,也就不难理解了。
2.5.4 通过let关键字实现块级作用域
为了解决前文中介绍的JavaScript(ECMAScript)语法中没有“块级作用域”这个问题,ECMAScript 6语法规范中增加了一个“let”关键字来实现“块级作用域”的功能。
下面来看一个关于let关键字的代码示例(详见源代码ch02目录中ch02-js-es6-let.html文件),该代码是在【代码2-38】的基础上修改而完成的。
【代码2-39】
01 <script type="text/javascript"> 02 function func_let() { 03 var i; 04 for (i = 0; i < 3; i++) { 05 console.log("i = " + i); 06 let j = i; 07 console.log("j = " + j); 08 } 09 console.log("j = " + j); 10 } 11 func_let(); 12 </script>
关于【代码2-39】的分析如下:
第04~08行代码定义一个for循环语句,其自变量就是变量(i)。比较特殊的是第06行代码,通过“let”关键字定义了第二个变量(j),并赋值为变量(i)的值;
第09行代码在for循环语句之后,再次在浏览器控制台窗口中输出变量(j)的值。
页面效果如图2.39所示。从浏览器控制台中输出的内容来看,【代码2-39】中第09行代码的变量(j)是未定义的,这就与【代码2-38】中第08行代码执行结果完全不同了。而从第07行代码输出的内容来看,在for循环语句内的变量(j)均获取了具体的,这就充分地说明第06行代码中,通过“let”关键字定义的变量(j)的有效作用域仅存在于第04~08行代码定义的“块级作用域(for循环语句)”内。
图2.39 通过let运算符实现块级作用域
2.5.5 let关键字使用规则
既然“let”关键字是ECMAScript 6语法规范中新增的了一个特性,那么其在使用规则上自然会与“var”关键字有所区别,在使用“let”关键字时要避免出现以下两种错误情形:
- 变量在使用“let”声明之前就使用会报错;
- 重复使用“let”声明同一变量会报错。
下面,先看一个关于let关键字使用规则的代码示例(详见源代码ch02目录中ch02-js-es6-let-rules-a.html文件)。
【代码2-40】
01 <script type="text/javascript"> 02 function func_let_rules() { 03 console.log("a = " + a); 04 let a = 1; 05 console.log("a = " + a); 06 } 07 func_let_rules(); 08 </script>
关于【代码2-40】的分析如下:
第04行代码通过“let”关键字定义第一个变量(a),并进行初始化赋值(a=1);
第03行和第05行代码分别尝试直接在浏览器控制台窗口中输出变量(a)的值。其中,第03行代码是在变量(a)声明定义之前,第05行代码是在变量(a)声明定义之后。
页面效果如图2.40所示。从浏览器控制台中输出的内容来看,【代码2-40】中第03行代码直接报错,在使用“let”关键字声明变量初始化之前是无法调用该变量的。
图2.40 let关键字使用规则(1)
下面,接着再看一个关于let关键字使用规则的代码示例(详见源代码ch02目录中ch02-js-es6-let-rules-b.html文件)。
【代码2-41】
01 <script type="text/javascript"> 02 function func_let_rules() { 03 let a = 1; 04 console.log("a = " + a); 05 let a = 2; 06 console.log("a = " + a); 07 } 08 func_let_rules(); 09 </script>
页面效果如图2.41所示。从浏览器控制台中输出的内容来看,【代码2-41】中第03行和第05行代码直接报错,使用“let”关键字是无法重新声明变量的。
图2.41 let关键字使用规则(2)
2.5.6 let关键字应用
前面铺垫这么多内容,接下来该是重点要介绍的内容。读者可能会有疑问,既然“let”关键字的功能也可以通过“var”关键字来实现,那么ECMAScript 6语法规范中新增“let”关键字的作用是什么呢?文字阐述往往没有实际代码表达得透彻,我们还是先看一个具体的代码示例。
下面是一个为了更好地介绍let关键字应用所进行铺垫的代码示例(详见源代码ch02目录中ch02-js-es6-let-usage-a.html文件)。
【代码2-42】
01 <script type="text/javascript"> 02 var arrJS = ["JavaScript", "EcmaScript", "jQuery"]; 03 for (var i = 0; i < 3; i++) { 04 console.log("arrJS[" + i + "] = " + arrJS[i]); 05 } 06 </script>
关于【代码2-42】的分析如下:
这段代码很简单,先定义一个字符串数组,然后通过for循环语句依次在浏览器控制台中输出每个数组项的内容。
页面效果如图2.42所示。浏览器控制台中依次输出了每个数组项的内容。【代码2-42】很简单,我们先铺垫该代码的目的是为了介绍下面的代码示例。
图2.42 let关键字应用(1)
下面接着看一个在【代码2-42】的基础上稍作改动的代码示例(详见源代码ch02目录中ch02-js-es6-let-usage-b.html文件)。
【代码2-43】
01 <script type="text/javascript"> 02 var arrJS = ["JavaScript", "EcmaScript", "jQuery"]; 03 for (var i = 0; i < 3; i++) { 04 setTimeout(function () { 05 console.log("arrJS[" + i + "] = " + arrJS[i]); 06 }, 500); 07 } 08 </script>
页面效果如图2.43所示。从浏览器控制台中输出的内容来看,第05行代码连续输出重复三次的数组项内容(undefined)。这个结果与图2.42的内容完全不同,这是什么原因呢?
图2.43 let关键字应用(2)
其实,主要原因还是变量的作用域造成的。第03行代码定义的for循环语句的自变量(i)是通过“var”关键字声明的,因此该自变量(i)的作用域是整个脚本代码空间。由于setTimeout()方法会设定延时,因此在for循环语句执行完毕后,第05行代码定义的在浏览器控制台中输出内容仍未执行,而此时自变量(i)的值已经变为3了。所以,最后等到第05行代码执行时获取已经是数组项arrJS[3](未定义,undefined)的内容。
【代码2-43】的问题主要就是JavaScript(ECMAScript)语法规范中没有“块级作用域”的概念造成的。那么如何解决呢?这时就该是前文中介绍的“let”关键字发挥作用的时刻了。
在【代码2-43】的基础上稍作改动,代码示例如下(详见源代码ch02目录中ch02-js-es6-let-usage-c.html文件)。
【代码2-44】
01 <script type="text/javascript"> 02 var arrJS = ["JavaScript", "EcmaScript", "jQuery"]; 03 for (let i = 0; i < 3; i++) { 04 setTimeout(function () { 05 console.log("arrJS[" + i + "] = " + arrJS[i]); 06 }, 500); 07 } 08 </script>
页面效果如图2.44所示。从浏览器控制台中输出的内容来看,其与图2.42中的内容完全相同,说明“let”关键字成功将自变量(i)的作用域限定在每一次for循环语句块内,因此也就能获取正常的数组项的内容。
图2.44 let关键字应用(3)
2.5.7 通过const关键字定义常量
在类似C和Java的这类高级语言中常量很常用,ECMAScript 6语法规范中也增加一个“const”关键字来实现常量定义的功能。
ECMAScript 6语法规范中的常量也适用于“块级作用域”,有点像使用“let”关键字定义的变量。与其他高级语言类似,ECMAScript的常量值同样不能通过重新赋值来改变,并且也不能重新进行声明。
常量声明的同时就要进行初始化,而且创建的值仅是一个只读引用,因此无法进行更改。但也有例外,如果创建的常量是一个引用的对象,就可以改变对象的内容(如对象的参数值)。
下面,先看一个关于const关键字的代码示例(详见源代码ch02目录中ch02-js-es6-const.html文件)。
【代码2-45】
01 <script type="text/javascript"> 02 /** 03 * const声明时必须初始化 04 */ 05 const myPI; 06 </script>
关于【代码2-45】的分析如下:
第05行代码尝试通过“const”关键字声明一个常量(myPI),但并没有进行初始化操作。
页面效果如图2.45所示。从浏览器控制台中输出的内容来看,JS调试器直接提示需要进行常量的初始化操作。
图2.45 const关键字(1)
下面,继续看一个关于const关键字的代码示例(详见源代码ch02目录中ch02-js-es6-const.html文件)。
【代码2-46】
01 <script type="text/javascript"> 02 /** 03 * const常量不能再进行赋值操作 04 * @type {number} 05 */ 06 const myPI = 3.1415926; 07 myPI = 3.14; 08 </script>
页面效果如图2.46所示。从浏览器控制台中输出的内容来看,JS调试器直接对常量再次赋值的初始化操作进行了报错。
图2.46 const关键字(2)
在【代码2-46】的基础上进行修改,完成一个关于const关键字的代码示例(详见源代码ch02目录中ch02-js-es6-const.html文件)。
【代码2-47】
01 <script type="text/javascript"> 02 /** 03 * const常量不能重复声明 04 * @type {number} 05 */ 06 const myPI = 3.1415926; 07 const myPI = 3.1415926; 08 </script>
页面效果如图2.47所示。从浏览器控制台中输出的内容来看,JS调试器直接对常量重复声明的操作进行了报错。
图2.47 const关键字(3)
另外,对于已经通过“const”关键字声明的常量,再次使用“var”或“let”关键字声明也是不被允许的。
前面的几个代码示例说明常量已经声明定义就无法再更改了,这点是毋庸置疑的。不过也有一种例外,那就是对象常量的参数值是可以更改的。不过需要注意,对象常量本身是不可更改的,仅仅是常量参数可以更改。
下面来看一个关于const关键字定义对象常量的代码示例(详见源代码ch02目录中ch02-js-es6-const.html文件)。
【代码2-48】
01 <script type="text/javascript"> 02 /** 03 * const声明对象常量 04 * @type {{key: string}} 05 */ 06 const myObj = {"key": "JavaScript"}; 07 console.log("key : " + myObj.key); 08 myObj.key = "EcmaScript"; 09 console.log("key : " + myObj.key); 10 </script>
页面效果如图2.48所示。从浏览器控制台中输出的内容来看,第08行代码成功修改了常量对象(myObj)中“key”的参数值。
图2.48 const关键字(4)