一、let
与const
先说结论:
let
和const
的用法类似var
,都是用来声明变量, 这两者的区别在于:const
声明一个只读的常量,一旦声明后就不能改变(实际上可以理解为const
声明的那个变量的内存地址不能改变,也就是说,如果用const
声明一个函数,函数内的值是可以改变的,只是不能重新指向另一个对象)。虽然用法类似,但比
var
好用得多,理由如下:
1、不存在变量提升
console.log(a); //undefined
var a = 1;
console.log(b); //Uncaught ReferenceError: Cannot access 'b' before initialization
let b = 2;
由于var
存在变量提升,以上代码不会报错,经常会引发很多 bug;而let
因为不存在变量提升,更方便检查错误。
2、存在暂时性死区(temporal dead zone)
ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。这就是暂时性死区。
ES6 规定暂时性死区和let
、const
语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
3、不允许重复声明
//报错
function func(){
let a = 1;
let a = 2;
}
- 在使用
for
循环的时候,let
还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。看栗子:
for (var i = 0;i<5;i++){
setTimeout(function(){console.log(i)},500);
}
//控制台打印结果
5
5
5
5
5
for(let i = 0;i<5;i++){
setTimeout(function(){console.log(i)},500)
}
//控制台打印结果
0
1
2
3
4
这是一道经典面试题,简单地说,之所以var
声明的时候打印出 5 个 5,是因为 500ms 后打印出的 i 已经循环完毕,且循环体内部只有一个 i,此时 i = 5,所以打印出来5 遍 5;而用 let
声明时,可以理解为循环体内部其实有 5 个 i,每个 i 代表一个值,所以打印出来的是 5 个 i 的值,分别是 0 到 4。
二、顶层对象的属性
顶层对象,在浏览器环境指的是window
对象,在 Node 指的是global
对象。ES5 之中,顶层对象的属性与全局变量是等价的。
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window
对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
window.a // 1
let b = 2;
window.b // undefined
上面代码中,a
由var
声明,所以是顶层对象的属性;b
由let
声明,不属于顶层对象的属性,所以返回undefined
。