let
let的用法类似于var,但是let所声明的变量只在它所在的代码块有效。
与var进行比较:
{
var a=100
let b=200
}
console.log(a) //100
console.log(b) //ReferenceError: b is not defined
我们可以看到,在块内声明的b在块作用域外不能被访问。
这一特性经常被用在for循环中:
//用var声明i的for循环
var a=[];
for(var i=0;i<3;i++){
a[i]=function(){
console.log(i)
}
}
a[2](); //3
在上面的例子中,i的值是用var声明的,在全局范围内都有效,所以经过了所有的循环,i的值会变成最后一次循环的值,所以数组a中的每一个值都为3
而用let声明i,每次循环,i的值只在其块级作用域内有效,所以a数组的值分别为1,2,3:
//用let声明i的for循环
var a=[];
for(let i=0;i<3;i++){
a[i]=function(){
console.log(i)
}
}
a[2](); //2
对for循环的补充
for循环中的声明循环变量和循环体是父级作用域和子代作用域的关系:
for(let i=0;i<3;i++){
let i='hello'
console.log(i)
}
//hello
//hello
//hello
let声明方式的特点
1.不存在变量提升
var声明的变量可以在变量被声明之前被使用,而let限制了这一点,如果在let声明之前被使用,会抛出错误。
console.log(a)
var a;//undefined
console.log(b)
let b;//ReferenceError: a is not defined
2.暂时性死区
只要块级作用域内存在let生命的变量,那么这个变量就会绑定这个区域,不会受外部同名变量的影响。
var a=100;
{
a=200 //ReferenceError: a is not defined
console.log(a) //ReferenceError: a is not defined
let a
a=300
console.log(a) //300
}
a在全局中声明,但是当块级作用域中用了他、声明了一个同名变量,这个变量就不能在声明前改变,相当于“划分了自己的领土范围”
一种更常见的死区:
function bar(x=y,y=2){
return[x,y]
}
bar()//ReferenceError: y is not defined
暂时性死区也意味着typeof不再是一个百分百安全的操作
console.log(typeof c)
let c=1; //ReferenceError: c is not defined
在这种情况下,type会抛出一个错误,而不是和var一样,返回undefined,所以这要求我们在使用let的时候,养成良好的编码习惯
3.不允许重复声明
let不允许在相同作用域内重复声明一个变量
//报错
function foo(){
let a=0;
var a=1;
}
在函数内重复声明参数也会报错
function func(arg){
let arg
}
const
const声明的是一个常量,一旦声明,常量的值就不能改变:
const a=1;
a=2 //TypeError: Assignment to constant variable.
只声明不赋值也会报错
const a//SyntaxError: Missing initializer in const declaration
const声明的特点
const的本质保证的并不是变量的值不能改变,而是变量指向的那个内存地址不能改变。
对于简单数据类型:就相当于值不能改变
对于复杂数据类型:相当于指针指向的内存地址不能改变
const foo={}
foo.prop=100
console.log(foo.prop) //100 可以添加属性,指针指向的内存地址不变
foo={}//TypeError: Assignment to constant variable. //当改变指针的指向时,会报错
如果想要冻结复杂数据类型的属性,可以用Object.freeze
完全冻结的方法:
var constantize=(obj)=>{
Object.freeze(obj)
Object.freeze(obj).forEach((key,i)=>{
if(typeof obj[key]==='object'){
constantize(obj[key])
}
})
}
参考书籍:《ES6标准入门(第3版)》 阮一峰