let
和 const
作ES6的一大新特性,日常使用最多,那么我们真的清楚他的特性了吗,以及了解Babel转换后的结果吗?
ES6 之前
- 使用关键字
var
声明变量。 - 变量的作用域为函数体内。
- 存在变量提升的特性。
- 重复声明不会出错。
- 全局声明的变量会作为顶层对象(如window)的属性。
1 | var foo = -1; |
在使用 var
进行循环时经常会出现如下问题,那么ES6中存在这样的问题吗?1
2
3
4
5
6for (let i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i);
}, 1000)
}
// 结果:5, 5, 5, 5, 5 并没有按照要求打印出0-4
ES6 let
不允许重复声明
全局对象不再是顶层对象
1 | var a = 1; |
不存在的变量提升
1 | console.log(a); // undefined |
块级作用域
1 | let a = 123; |
虽然不存在变量提升的问题,但是如代码示例,由于大括号内声明了let,导致运行时出现ReferenceError错误。
那这不就是声明提前了么🤷?
循环中的问题
1 | for (let i = 0; i < 5; i++) { |
在使用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
7const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
使用ES5创建一个不可改对象 freeze
1 | const foo = Object.freeze({}); |
babel转换
let
和const
会被编译成var
临时死区特性消失
1 | // 源码 |
重复定义编译时会出错
解决了循环时引用问题
1 | // 源码 |