ES6学习笔记之变量声明

​ ES6在原有var关键字的基础上,增加了letconst两个关键字用来声明变量。新增加的关键字在使用上更为严谨,解决了很多原来var声明变量时造成的问题。

  之前跟着Blue的ES6课程过了一遍ES6的基本语法,后来全栈课程里也听了一遍ES6,但是感觉还是不太系统,所以跟着阮一峰老师的《ECMAScript 6入门》又学了一遍,在此要强烈推荐阮一峰老师的这本ES6入门,真的非常细致。我也把笔记大致整理了一下,有一些高级的内容还没太明白,弄明白的我争取把每个内容都自己叙述一遍。

  ES6较ES5在变量声明上增加了letconst两个新的关键字,用法上也有一些区别。我做了一个对比表格。

关键词        特性 作用域 变量提升
var - 声明变量
- 可以重复声明
- 后一次声明会覆盖前一次
函数级作用域 会提升
let - 声明变量
- 无法重复声明
块级作用域 不会提升
const - 声明常量
- 无法重复声明
- 声明必须赋值
- 可以修改对象属性
块级作用域 不会提升

1. 声明

   var用于声明变量,且可以重复声明,后一次声明会覆盖前一次声明。

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

  let用于声明变量,const用于声明常量,两者都无法重复声明,否则会报错,其中,const声明常量时必须赋值

2. 变量提升 Hoisting

  var声明变量时,会变量提升。

所谓变量提升,就是无论任何时候对变量赋值,都会在作用域头部给变量添加无赋值声明(无赋值很重要,之前弄错很多次)

1
2
3
4
5
6
7
8
var a = 0;
function fn() {
//var a
//此处会声明a,但是并未赋值
console.log(a) //undefined
var a = 10;
console.log(a); //10
}

  letconst不存在变量提升,经典面试问题如:

1
2
3
4
5
6
let str = 'hello';
//实际上var i被提升到了头部
for(var i = 0; i < str.length; i ++) {
console.log(str[i]);
}
console.log(i); //5

  而如果是letconst,变量不会提升到头部,打印i就会出现未定义的问题。

1
2
3
4
5
let str = 'hello';
for(let i = 0; i < str.length; i ++) {
console.log(str[i]);
}
console.log(i); // i is not defined

3. 函数级作用域 VS 块级作用域

  ES5中只有全局作用域函数作用域,没有块级作用域。函数作用域是指在函数中定义的变量,函数外部不能获取变量。但由于var声明的变量存在变量提升,因此会导致一些问题。

问题1: 内部变量可能会覆盖外部变量

1
2
3
4
5
6
7
8
9
10
11
let temp = new Date();

function fn() {
//var temp
//由于变量提升,var temp的无赋值声明会提升到函数头部
console.log(temp);
if(false) {
var temp = "hello world";
}
}
fn() // temp is not defined

问题2: 循环变量会污染全局变量

1
2
3
4
5
6
7
var str = 'hello';

for(var i = 0; i < str.length; i ++) {
console.log(str[i]);
}
console.log(i); //5
// i 原本用来控制循环,但是循环结束后,i并没有消失,而是泄露为全局变量

  ES6中新增了块级作用域。

1
2
3
4
5
6
7
8
var str = 'hello';
function d() {
console.log(str);
if(false) {
let str = 'world';
}
}
d(); // 'hello'

  let生成的str的值不会对输出结果产生任何影响,因为let声明的str的作用域是if内部的块级作用域。如果我们在if内部添加一个console.log(str),同时将前一个console.log(str)放到if后面会看得更明白,let声明的内容只在if内部起作用。

1
2
3
4
5
6
7
8
9
var str = 'hello';
function d() {
console.log(str);
if(false) {
let str = 'world';
console.log(str);
}
}
d(); // 'hello'
1
2
3
4
5
6
7
8
9
10
11
var str = 'hello';
function d() {
if(true) {
let str = 'world';
console.log(str);
}
console.log(str);
}
d();
// 'world'
// 'hello'

4. 暂时性死区(TDZ)

暂时性死区 Temporary Dead Zone
如果区块中使用了letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域
即声明之前,变量就已经存在,但是不可获取(使用)

1
2
3
4
5
6
var str = 'hello';
function d() {
console.log(str);
let str = 'world';
}
d(); // str is not defined

  还是上面一个例子,如果将if去掉,会发现输出结果变成了str is not defined,因为去掉if之后,函数内部会形成TDZ,防止在let之前,str被重复声明,因此console.log(str)实际在str被声明之前输出,结果是str未定义。

5. const修改属性

  前面说过,const声明变量必须赋值,且一旦赋值将不能改变。本质上是变量指向的内存地址不能改变

对于简单类型的变量来说,即不能改变值;
对于复合类型变量(如数组,json),可以修改符合类型变量的属性

1
2
3
4
5
6
7
8
9
//数组
const arr = [1,2,3,4];
arr[2] = 5;
console.log(arr); //[1,2,5,4]

//json
const obj = {name: 'Tom', age:19};
obj.age = 30;
console.log(obj); //{name:'Tom', age:30}

可以通过冻结对象(Object.freeze()方法)来避免const声明的复合类型变量被修改

1
2
3
4
5
6
7
8
 //数组
const arr = Object.freeze([1,2,3]);
arr[0] = 12;
console.log(arr); //[1,2,3]

const obj = Object.freeze({age:18});
obj.age = 30;
console.log(obj); // {age:18} 这样就实现冻龄永远18岁了

6. 顶层对象属性

  ES5中,顶层对象属性和全局变量等价

1
2
window.a = 1;
console.log(a); //1

  ES6对这一现象进行了改进,保留了原有的varfunction声明的全局变量是顶层对象属性。
  但是let,constclass声明的全局对象将不属于顶层对象属性。