目录
前面我们声明变量、定义函数等等都是基于ES5的基础之上
ES6概述
ES6全称是ECMAScript6.0,是JavaScript的下一个版本标准。ECMAScript和JavaScript的关系可以这么理解:前者是后者的规格,后者是前者的一种实现
ES5主要是为了解决ES5的先天不足,例如ES5中没有类的概念,也没有块级作用域等等,这些ES6都有很好的弥补不足。
let和const命令
这两个命令在之前的博客-作用域详解中我们有提到过,这里再做一个系统性的总结,区分var命令,之前的博客:
let命令
let命令的用法类似于var,不同点在于声明的变量只在let命令所在的代码块内有效。
{
let a = 1;
console.log(a); //1
}
console.log(a); //ReferenceError: a is not defined
我们可以对比一下var输出和let输出:
var a = [];
for(var i = 0;i<5;i++){
a[i] = function(){
console.log("var输出",i);
};
}
a[3](); //var声明输出 5
var b = [];
for(let j = 0;j<5;j++){
b[j] = function(){
console.log("let输出",j);
};
}
b[3](); //let声明输出 3
如上例,变量i是var变量声明,是一个全局变量。当函数内循环完之后,i输出的是最后一轮的值i=5,所以最终全局变量i为5。变量j是let变量声明的,是局部变量,只在本轮循环有效,也就是每一轮的循环变量j都是一个新变量。
const命令
const与let的区别在于,const声明的是一个只读的常量,声明之后常量的值不能被改变。所以const声明的变量不能只声明不赋值。
//const正确声明和使用
const a = 10;
console.log(a); //2
//改变const声明常量的值--报错
a = 2;
console.log(a); //TypeError: Assignment to constant variable.
//const声明不赋值--报错
const b; //SyntaxError: Missing initializer in const declaration
const的本质不是保证变量的值不改动,实则是保证变量指向的内存地址所保存的数据不得改动。因此对于基本类型数据,值就保存在变量指向的内存地址,也就相当于常量。但是对于引用数据类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针。那么这个时候,const只能保证这个指针是固定的,但是它指向的数据结构就是可变的了。因此声明一个常量的时候,要先考虑到指向的数据是什么类型。
//声明一个对象为常量
const obj = {};
//在对象obj内添加属性--可行
obj.a = 1;
console.log(obj); //{ a: 1 }
//将对象obj指向为另一个对象--报错
obj = {}; //TypeError: Assignment to constant variable.
如果我们想要冻结对象,就可以使用Object.freeze()方法
//冻结对象
const obj = Object.freeze({});
obj.a = 1;
console.log(obj); //{}
上例中,常量obj指向的就是一个冻结的对象,因此添加新属性就不起作用。但是该方法不能冻结多层对象:
const obj = {
a:10,
num:{
b:20
}
}
Object.freeze(obj); //冻结obj对象
obj.a = 1;
obj.num.b = 2;
console.log(obj); //{ a: 10, num: { b: 2 } }
很显然,上例中我们发现属性a不能被修改,但是属性b还是被修改了,没有被冻结,那么要想解决此问题,达到冻结多层对象的效果,我们可以这么做:
const obj = {
a:10,
num:{
b:20
}
}
function deepFreeze(obj){
Object.freeze(obj);
for(let key in obj){
if(obj.hasOwnProperty(key) && typeof obj[key]==='object'){
deepFreeze(obj[key]);
}
}
}
deepFreeze(obj); //冻结obj对象
obj.a = 1;
obj.num.b = 2;
console.log(obj); //{ a: 10, num: { b: 20 } }
通过上述代码可以看出,我们封装一个deepFreeze函数,内部通过for...in遍历对obj对象中的所有属性进行冻结,如此达到冻结多层对象的效果。【hasOwnProperty()方法我们有在之前的博客对象object详解中提到过,这是一个内置对象,作用是指示对象自身属性中是否具有指定的属性】
使用特点
1.只在声明的块级作用域内有效
这个在上面let命令中我们已经演示过了,这里就不多加赘述
2.不允许重复声明
我们知道使用var变量时,可以重复声明,之后声明的值会覆盖之前声明的值。但是let和const不允许重复声明,重复声明会报错。
//var重复声明
var a = 1;
var a = 2;
var a = 3;
console.log(a); //3
// let重复声明--报错
let b = 1;
let b = 2;
console.log(b); //SyntaxError: Identifier 'b' has already been declared
var c = 1;
let c = 2;
console.log(c); //SyntaxError: Identifier 'c' has already been declared
所以同样,不能在函数内重新声明参数,但是若函数内还有一个子函数或者其他块级作用域,就可以进行声明,相当于外部函数是一个父作用域,内部其他块级作用域是一个子作用域。
//函数内重新声明参数--错误
function Fun(arg){
let arg = 1;
console.log(arg);
}
Fun(); //SyntaxError: Identifier 'arg' has already been declared
//参数内的块级作用域声明参数--正确
function Fun(arg){
{let arg = 1;
console.log(arg);}
}
Fun(); //1
故let和const命令在函数内和循环内使用就非常合适,需要注意的是,在for循环内,有一个地方需要注意:
for(let i=0;i<5;i++){
let i = 10;
console.log(i); //10 10 10 10 10
}
我们发现,在for循环内用let两次对i进行声明赋值,但是没有报错,并且输出的是第二次声明的值。这是因为,在for循环中,设置循环变量的该部分是一个父作用域, 循环体内部是一个单独的子作用域,也就是这两个作用域是单独的,不是同一个作用域,所以不是两次声明赋值。
3.不存在变量提升
在下面这篇博客我们有总结过,var关键字声明的变量存在变量提升情况。let和const命令就不存在这种情况,也就是说let所声明的变量和const所声明的常量一定要在声明之后使用
//var声明变量
console.log(a); //undefined
var a = 1;
//let声明变量
console.log(b); //ReferenceError: Cannot access 'b' before initialization
let b = 2;
//const声明常量
console.log(c); //ReferenceError: Cannot access 'c' before initialization
const c = 3;
4.存在暂时性死区
若块级作用域中存在let命令,它所声明的变量就绑定在这个区域,不受外部影响。若在这个块级作用域之前,在外部同样声明相同的变量,因为该变量已经绑定了块级作用域,在使用let命令声明该变量之前,该变量都无法使用。这就称之为“暂时性死区”。const如是,我们使用let进行测试了解:
var a = 10;
if(1){
a = 20;
let a;
}
console.log(a); //ReferenceError: Cannot access 'a' before initialization
上例中,我们先声明了一个全局变量a,然后在一个块级作用域中对a进行重新赋值,再使用let进行声明,结果a无法输出,这就是暂时性死区导致的结果。我们进行仔细分析: 虽然我们生命了全局变量a,但是块级作用域中又用let声明了一个局部变量a,所以变量a绑定在了这个局部作用域内。因此在let声明变量之前,对于a的赋值都会报错。
因此,若区块中存在let和const命令,这个区块对这些命令声明的变量会形成封闭作用域,若在声明之前使用这些变量会报错。
var a = 10;
if(1){
let a;
console.log(a); //undefined
a = 20;
console.log(a); //20
}
console.log(a); //10