七、ES6
从这一章节开始,我们来看一下关于ES6
的重点内容。
1、let 关键字
1.1 基本用法
ES6中新增了let命令,用于变量的声明,基本的用法和var类似。例如:
<script>
// 使用var使用声明变量
var userName = "bxg";
console.log("userName=", userName);
// 使用let声明变量
let userAge = 18;
console.log("userAge=", userAge);
</script>
通过以上的代码,我们发现var和let的基本使用是类似的,但是两者还是有本质的区别,最大的区别就是:
使用let所声明的变量只在let命令所在的代码块中有效。
1.2 let与var区别
下面我们通过一个for循环的案例来演示一下let和var的区别,如下所示:
<script>
for (var i = 1; i <= 10; i++) {
console.log("i=", i)
}
console.log("last=", i)
</script>
通过以上的代码,我们知道在循环体中i的值输出的是1–10,最后i的值为11.
但是如果将var换成let会出现什么问题呢?代码如下:
for (let i = 1; i <= 10; i++) {
console.log("i=", i)
}
console.log("last=", i)
在循环体中输出的i的值还是1–10,但是循环体外部打印i的值时出现了错误,错误如下:
出错的原因是:通过let声明的变量只在其对应的代码块中起作用,所谓的代码块我们可以理解成就是循环中的这一对大括号。
当然在这里我们通过这个提示信息,可以发现在ES6中默认是启动了严格模式的,严格模式的特征就是:变量未声明不能使用,否则报的错误就是变量未定义。
那么在ES5中怎样开启严格模式呢?我们可以在代码的最开始加上:“use strict”
刚才我们说到,let声明的变量只在代码块中起作用,其实就是说明了通过let声明的变量仅在块级作用域内有效
1.3 块级作用域
1.3.1 什么是块级作用域?
在这里告诉大家一个最简单的方法: **有一段代码是用大括号包裹起来的,那么大括号里面就是一个块级作用域**
也就是说,在我们写的如下的案例中:
for (let i = 1; i <= 10; i++) {
console.log("i=", i)
}
console.log("last=", i)
i 这个变量的作用域只在这一对大括号内有效,超出这一对大括号就无效了。
1.3.2 为什么需要块级作用域?
ES5 只有全局作用域和函数作用域,没有块级作用域,这样就会带来一些问题,
第一:内层变量可能会覆盖外层变量
代码如下:
var temp = new Date();
function show() {
console.log("temp=", temp)
if (false) {
var temp = "hello world";
}
}
show();
执行上面的代码,输出的结果为 temp=undefined ,原因就是变量由于提升导致内层的temp变量覆盖了外层的temp变量
第二: 用来计数的循环变量成为了全局变量
关于这一点,在前面的循环案例中,已经能够看到。在这里,可以再看一下
<script>
for (var i = 1; i <= 10; i++) {
console.log("i=", i)
}
console.log("last=", i)
</script>
在上面的代码中,变量i的作用只是用来控制循环,但是循环结束后,它并没有消失,而是成了全局的变量,这不是我们希望的,我们希望在循环结束后,该变量就要消失。
以上两点就是,在没有块级作用域的时候,带来的问题。
下面使用let来改造前面的案例。
let temp = new Date();
function show() {
console.log("temp=", temp)
if (false) {
let temp = "hello world";
}
}
show();
通过上面的代码,可以知道let不像var那样会发生“变量提升”的现象。
第二个问题前面已经讲解过。
1.3.3 ES6块级作用域
let实际上为JavaScript新增了块级作用域,下面再看几个案例,通过这几个案例,巩固一下关于“块级作用域”这个知识点的理解,同时进一步体会块级作用域带来的好处
<script>
function test() {
let num = 5;
if (true) {
let num = 10;
}
console.log(num)
}
test()
</script>
上面的函数中有两个代码块,都声明了变量num,但是输出的结果是5.这表示外层的代码不受内层代码块的影响。如果使用var定义变量num,最后的输出的值就是10.
说一下,下面程序的输出结果是多少?
if (true) {
let b = 20;
console.log(b)
if (true) {
let c = 30;
}
console.log(c);
}
输出的结果是:b的值是20,在输出c的时候,出现了错误。
导致的原因,两个if就是两个块级作用域,c这个变量在第二个if中,也就是第二个块级作用域中,所以在外部块级作用域中无法获取到变量c.
块级作用域的出现,带来了一个好处以前获得广泛使用的立即执行匿名函数不再需要了。
下面首先定义了一个立即执行匿名函数:
;(function text() {
var temp = 'hello world';
console.log('temp=', temp);
})()
匿名函数的好处:通过定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个“私有”的空间,该空间内的变量和方法,不会破坏污染全局的空间 。
但是以上的写法是比较麻烦的,有了“块级作用域”后就编的比较简单了,代码如下:
{
let temp = 'hello world';
console.log('temp=', temp);
}
通过以上的写法,也是创建了一个“私有”的空间,也就是创建了一个封闭的作用域。同样在该封闭的作用域中的变量和方法,不会破坏污染全局的空间。
但是以上写法比立即执行匿名函数简单很多。
现在问你一个问题,以下代码是否可以:
let temp = '你好';
{
let temp = 'hello world';
}
答案是可以的,因为这里有两个“块级作用域”,一个是外层,一个是内层,互不影响。
但是,现在修改成如下的写法:
let temp = '你好';
{
console.log('temp=', temp);
let temp = 'hello world';
}
出错了,也是变量未定义的错误,造成错误的原因还是前面所讲解的let 不存在“变量提升”。
块级作用域还带来了另外一个好处,我们通过以下的案例来体会一下:
该案例希望不同时间打印变量i的值。
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log('i=', i);
}, 1000)
}
那么上面程序的执行结果是多少?
对了,输出的都是 i=3
造成的原因就是i为全局的。
那么可以怎样解决呢?相信这一点对你来说很简单,在前面ES5课程中也讲过。
for (var i = 0; i < 3; i++) {
(function(i) {
setTimeout(function() {
console.log('i=', i);
}, 1000)
})(i)
}
通过以上的代码其实就是通过自定义一个函数,生成了函数的作用域,i变量就不是全局的了。
这种使用方式很麻烦,有了let命令后,就变的非常的简单了。
代码如下:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log('i=', i);
}, 1000)
}
1.4 let命令注意事项
1.4.1 不存在变量提升
let不像var那样会发生“变量提升”现象。所以,变量一定要在声明后使用,否则会出错。
关于这一点,前面的课程也多次强调。
console.log(num);
let num = 2;
1.4.2 暂时性死区
什么是暂时性死区呢?
先来看一个案例:
var num = 123;
if (true) {
num = 666;
let num;
}
上面的代码中存在全局的变量num,但是在块级作用域内使用了let又声明了一个局部的变量num,导致后面的num绑定到这个块级作用域,所以在let声明变量前,对num进行赋值操作会出错。
所以说,只要在块级作用域中存在let命令,它所声明的变量就被“绑定”在这个区域中,不会再受外部的影响。
关于这一点,ES6明确规定,如果在区域中存在let命令,那么在这个区域中通过let命令所声明的变量从一开始就生成了一个封闭的作用域,只要在声明变量前使用,就会出错。
所以说,所谓的“暂时性死区”指的就是,在代码块内,使用let命令声明变量之前,该变量都是不可用的。
1.4.3 不允许重复声明
let 不允许在相同的作用域内重复声明一个变量,
如果使用var声明变量是没有这个限制的。
如下面代码所示:
function test() {
var num = 12;
var num = 20;
console.log(num)
}
test()
以上代码没有问题,但是如果将var换成let,就会出错。如下代码所示:
function test() {
let num = 12;
let num = 20;
console.log(num)
}
test()
当然,以下的写法也是错误的。
function test() {
var num = 12;
let num = 20;
console.log(num)
}
test()
同时,还需要注意,不能在函数内部声明的变量与参数同名,如下所示:
function test(num) {
let num = 20;
console.log(num)
}
test(30)
2、const命令
2.1 基本用法
const用来声明常量,常量指的就是一旦声明,其值是不能被修改的。
这一点与变量是不一样的,而变量指的是在程序运行中,是可以改变的量。
let num = 12;
num = 30;
console.log(num)
以上的代码输出结果为:30
但是通过const命令声明的常量,其值是不允许被修改的。
const PI = 3.14;
PI = 3.15;
console.log(PI)
以上代码会出错。
在以后的编程中,如果确定某个值后期不需要更改,就可以定义成常量,例如:PI,它的取值就是3.14,后面不会改变。所以可以将其定义为常量。
2.2 const命令注意事项
2.2.1 不存在常量提升
以下代码是错误的
console.log(PI);
const PI = 3.14
2.2.2 只在声明的块级作用域内有效
const命令的作用域与let命令相同:只在声明的块级作用域内有效
如下代码所示:
if (true) {
const PI = 3.14;
}
console.log(PI);
以上代码会出错
2.2.3 暂时性死区
const命令与let指令一样,都有暂时性死区的问题,如下代码所示:
if (true) {
console.log(PI);
const PI = 3.14;
}
以上代码会出错
2.2.4 不允许重复声明
let PI = 3.14;
const PI = 3.14;
console.log(PI);
以上代码会出错
2.2.5 常量声明必须赋值
使用const声明常量,必须立即进行初始化赋值,不能后面进行赋值。
如下代码所示:
const PI;
PI = 3.14;
console.log(PI);
以上代码会出错
3、解构赋值
3.1、数组解构赋值基本用法
所谓的解构赋值,就是从数组或者是对象中提取出对应的值,然后将提取的值赋值给变量。
首先通过一个案例,来看一下以前是怎样实现的。
let arr = [1, 2, 3];
let num1 = arr[0];
let num2 = arr[1];
let num3 = arr[2];
console.log(num1, num2, num3);
在这里定义了一个数组arr,并且进行了初始化,下面紧跟着通过下标的方式获取数组中的值,然后赋值给对应的变量。
虽然这种方式可以实现,但是相对来说比较麻烦,ES6中提供了解构赋值的方式,代码如下:
let arr = [1, 2, 3];
let [num1, num2, num3] = arr;
console.log(num1, num2, num3);
将arr数组中的值取出来分别赋值给了,num1,num2和num3.
通过观察,发现解构赋值等号两侧的结构是类似。
下面再看一个案例:
let arr = [{
userName: 'zs',
age: 18
},
[1, 3], 6
];
let [{
userName,
age
},
[num1, num2], num3
] = arr;
console.log(userName, age, num1, num2, num3);
定义了一个arr数组,并且进行了初始化,arr数组中有对象,数组和数值。
现在通过解构赋值的方式,将数组中的值取出来赋给对应的变量,所以等号左侧的结构和数组arr的结构是一样的。
但是,如果不想获取具体的值,而是获取arr数组存储的json对象,数组,那么应该怎样写呢?
let arr = [{
userName: 'zs',
age: 18
},
[1, 3], 6
];
let [jsonResult, array, num] = arr;
console.log(jsonResult, array, num);
3.2、注意事项
3.2.1 如果解析不成功,对应的值会为undefined.
let [num1, num2] = [6]
console.log(num1, num2);
以上的代码中,num1的值为6,num2的值为undefined.
3.2.2 不完全解构的情况
所谓的不完全解构,表示等号左边只匹配右边数组的一部分。
代码如下:
let [num1, num2] = [1, 2, 3];
console.log(num1, num2);
以上代码的执行结果:num1=1,num2 = 2
也就是只取了数组中的前两个值。
// 如果只取第一个值呢?
let [num1] = [1, 2, 3];
console.log(num1);
//只取第二个值呢?
let [, num, ] = [1, 2, 3];
console.