【ES6复习】let和const

letconst 作ES6的一大新特性,日常使用最多,那么我们真的清楚他的特性了吗,以及了解Babel转换后的结果吗?

ES6 之前

  • 使用关键字 var 声明变量。
  • 变量的作用域为函数体内。
  • 存在变量提升的特性。
  • 重复声明不会出错。
  • 全局声明的变量会作为顶层对象(如window)的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var foo = -1;
// window.foo === -1

function fun() {
// 未抛出异常,声明提前
console.log('-1:', foo);

var foo = 0;
console.log('0:', foo);

if (true) {
console.log('1:', foo);
var foo = 1;
console.log('2:', foo);
}

// foo === 1 覆盖了前面声明的foo变量
console.log('3:', foo);
}
fun();
// undefined、0、0、1、1

在使用 var 进行循环时经常会出现如下问题,那么ES6中存在这样的问题吗?

1
2
3
4
5
6
for (let i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i);
}, 1000)
}
// 结果:5, 5, 5, 5, 5 并没有按照要求打印出0-4

ES6 let

不允许重复声明

全局对象不再是顶层对象

1
2
3
4
5
var a = 1;
console.log(window.a) // 1

let b = 1;
console.log(window.b) // undefined

不存在的变量提升

1
2
3
4
5
console.log(a); // undefined
var a = 1;

console.log(b); // ReferenceError
let b = 2;

块级作用域

1
2
3
4
5
6
let a = 123;
{
// console.log(a); // 暂时性死区导致代码报错
let a = 234;
}
console.log(a);

虽然不存在变量提升的问题,但是如代码示例,由于大括号内声明了let,导致运行时出现ReferenceError错误。

那这不就是声明提前了么🤷‍?

循环中的问题

1
2
3
4
5
6
7
for (let i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i);
}, 1000)
}
// console.log(i); // ReferenceError
// 结果:0,1,2,3,4

在使用let进行循环时出现了我们想要的结果,为什么呢?

首先分析一下,由于 var 声明的i是全局的,这就导致每次循环i就被重新赋值,而根据事件循环相关知识我们知道setTimeout是在同步代码for循环之后才会执行,所以每次输出结果都是5。

而在使用let声明的i不再是全局的,只在循环体内有效,所以在循环体外引用肯定会报错。再则就是每次循环都类似于重新声明i的变量,所以在循环体内部能正常使用i并输出。

感觉循环语句可以理解为一个函数作为父作用域声明了i;而循环体内部通过{}形成一个单独的子作用域,可以使用或修改i,甚至可以重新定义i

修改i的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 修改了i的值导致循环次数变少
for (let i = 0; i < 5; i++){
i++;
console.log(i);
}

// 对比理解
function loop(){
let i = 1;

{
// 块级作用域操作父作用域变量
i++;
console.log(i);
}
}

重新定义i:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 输出hahhaha
for (let i = 0; i < 5; i++){
let i = 'hahhaha'
console.log(i);
}

// 对比理解
function loop(){
let i = 1;

{
// 块级作用域当然可以重新定义i
let i = 'hahhaha';
console.log(i);
}
}

ES6 const

const用来声明一个只读常量,声明时必须赋值,且不能重新赋值,其他特效和 let 相似。

const实质是保证变量指向的内存地址不变更。所以对于如数值、字符串等基本类型由于数据名和值直接存储在栈中,所以不能可变更。而对于复杂类型,由于栈中存储的是数据名和堆的地址,所以改变复杂类型的属性是允许的。

1
2
3
4
5
6
7
const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

使用ES5创建一个不可改对象 freeze

1
2
3
4
5
const foo = Object.freeze({});

// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

babel转换

letconst 会被编译成var

临时死区特性消失

1
2
3
4
5
6
7
8
// 源码
console.log(a);
let a = 100;

// 编译结果
"use strict";
console.log(a);
var a = 100;

重复定义编译时会出错

解决了循环时引用问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 源码
for (let i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i)
})
}


// 编译结果
"use strict";
var _loop = function _loop(i) {
setTimeout(function () {
console.log(i);
});
};

for (var i = 0; i < 5; i++) {
_loop(i);
}