ES6-var和let/const比较

1. let命令

用法
let用来声明变量;但是let所声明的变量只有在let命令所在的代码块内有效。

1
2
3
4
5
6
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined
b // 1

1
2
3
4
5
6
7
var a = [];
for(var i=0; i<10; i++){
a[i] = function(){
console.log(i); // 循环结束,全局变量i的值为10;
}
}
a[6](); // 10

上述代码中,变量i是var声明的,在全局范围内都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出的是最后一轮的i的值。

1
2
3
4
5
6
7
var a = [];
for(let i=0; i<10; i++){
a[i] = function(){
console.log(i);
};
}
a[6]();

上述代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6;

不存在变量提升

1
2
3
4
5
// 脚本运行时,变量foo已经存在了,但是没有赋值,即undefined
console.log(foo); // undefined
console.log(bar); // ReferenceError
var foo = 2; // 变量提升;
let bar = 2; // 变量不会提升,所以在声明之前调用会报错;

暂时性死区

1
2
3
4
5
var tmp = 123;
if(true){
tmp = 'abc';
let tmp;
}

上述代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定了这个块级作用域,所以在let声明变量前,对tmp赋值会报错;

1
2
3
// 暂时性死区意味着typeof不再是一个百分百安全的操作了;
typeof x; // ReferenceError
let x;

ES6规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
暂时性死区就是:只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量之后才可以使用该变量。

不允许重复声明

1
2
3
4
5
6
7
function(){
let a = 10;
var a = 1; // 报错
}
function fun(arg){
let arg; // 报错
}

2.块级作用域

ES5中没有块级作用域的bug
内层变量覆盖外层变量

1
2
3
4
5
6
7
8
var tmp = new Date();
function f(){
console.log(tmp); // 提升后的内部变量覆盖了外部变量,且值为undefined
if(false) {
var tmp = 'hello'; // 变量提升;
}
}
f(); // undefined;

用来计数的循环变量泄漏为全局变量

1
2
3
4
5
6
var s = 'hello';
for(var i=0; i<s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5;
// 变量i只用来控制循环,但是循环结束后,它并没有消失,泄漏成了全局变量。

ES6的块级作用域

1
2
3
4
5
6
7
8
9
// let实际上为JavaScript新增了块级作用域
function f(){
let n = 5;
if(true) {
let n = 10;
}
console.log(n); // 5;
// 外层代码块不受内层代码块的影响;
}

ES6允许块级作用域的任意嵌套,且外层作用域无法读取内层作用域的变量

1
{{{ let insane = 'hell' }}}

块级作用域的出现,实际上是替代了立即执行匿名函数(IIFE)

1
2
3
4
5
6
7
8
// IIFE
(function(){
var tmp = ...;
}());
// ES6
{
let tmp = ...;
}

ES6引入的块级作用域,明确允许在块级作用域之中声明函数;并且块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

1
2
3
4
5
6
7
8
9
// ES5
function f() {console.log('outside'); }
(function() {
if(false){
// 这里的函数声明会被提升到if语句外部
function f(){ console.log('inside'); }
}
f(); // inside;
}());

1
2
3
4
5
6
7
8
9
10
// ES6
function f() {console.log('outside'); }
(function() {
var f;
if(false){
// 这里的函数声明处于if语句块级作用域内,外部不可引用
function f(){ console.log('inside'); }
}
f(); // f is not a function;
}());

应该避免在块级作用域内声明函数;如需要,应该写成函数表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}

// 函数表达式
{
let a = 'secret';
let f = function(){
return a;
}
}

3.const命令

const声明一个只读的常量;一旦声明,常量的值就不能改变。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const的作用域与let命令相同:只在声明所在的块级作用域内有效。

1
2
3
4
if(true) {
const max = 5;
}
max // max is not defined

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

1
2
3
4
if(true) {
console.log(max); // max is not defined
const max = 5;
}

对于符合类型的变量,变量名不指向数据,而是指向数据所在的地址;const命令只是保证变量名指向的地址不变,并不保证该地址的的数据不变;

1
2
3
4
const a = [];
a.push('hello');
a.length = 0;
a = ['Dave'] // 报错

上述代码中,常量a是一个数组,这个数组本身是可写的,但是如果将梁一个数组赋值给a,就会报错。

ES5只有两种声明变量的方法:var和function
ES6有:var、function、let、const、import和class;共6种声明变量的方法。

4.全局对象的属性

全局对象是最顶层的对象,在浏览器环境值的是Window对象,在Node.js指的是global对象。
ES5中,全局对象的属性与全局变量是等价的。
ES6中,var和function声明的全局变量,依旧是全局对象的属性;而let、congst、class声明的全局变量,不属于全局对象的属性。

1
2
3
4
5
var a = 1;
window.a; // 1

let b = 1;
window.b; // undefined