# 变量声明

## var的缺陷

在说`let`和`const`前，我们先来看看`var`有哪些让人忍不住吐槽的地方。

传统的`var`声明的变量是函数级作用域

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709122255627.png)

对于`var`声明的变量来说，**只存在函数级作用域和全局作用域。**

这种宽泛的作用域对于`if`、`for`的语法不太友好，试想一下以下代码：

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709123419447.png)

你本来是想要获取`-1`却不小心获取到了在`for`代码块里声明的第二个`i`变量。

同时，从上面代码我们也能看到第二个`var`的缺点，**就是在一个作用域里可以重复声明同一个变量**

例如在下方代码里，`index`将会被重复声明，而js并不会报错。

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709123924391.png)

`var`能重复声明的机制会让你很轻易地破坏原有的变量，在代码很少的时候或许你能谨慎地尽量不重复声明，但是当代码很多的时候，意外地重复声明变量是难以避免的。

此外`var`还存在着一种令人困惑的机制：变量提升，即变量可以在声明之前使用，值为`undefined`

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709122917767.png)

js会先把`var a` 提升到作用域的顶部，然后到原代码位置时才执行赋值操作`a = 1`。

正是由于`var`这些反人类的设计，ES6之后出现了新的声明变量的方式`let`和`const`

## let

`let`是ES6新增语法，用来声明变量，作为`var`的替代品

`let`作为ES6新声明变量的方式，必定是要修复传统`var`的缺陷。

请记住`let`和`const`的主要特性

* 块级作用域
* 不能重复声明
* 没有变量提升
* 暂时性死区

### 什么是块级作用域

前面说过`var`是函数级作用域和全局作用域，那么什么是块级作用域呢？

我们可以先拆分一下： “块级” + “作用域”

作用域我们都熟悉，那么什么是块呢？

所谓的块，就是代码块，有一对大括号包裹的区域。

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709125138700.png)

### 块级作用域

`let`声明的变量是块级作用域，换句话来说，**就是`let`声明的变量的作用域在它声明时所在的代码块里**

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709125540018.png)

在块（作用域）外无法访问该变量，这与var声明的变量在函数外无法访问函数内声明的变量是一样的。

### 不能重复声明

在一个作用域里，无法用`let`重复声明同一个变量。

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709125906653.png)

这样可以尽量避免变量被覆盖的情况。

### 不存在变量提升

前面说过`var`的变量提升机制，而`let`声明的变量则是必须声明后再使用，否则会报错。

![](https://677070670-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0mk9PpeFeJBaRkgdLW71%2Fuploads%2FOiakNuKq3MhGsX2OxzDG%2Fimage-20210709130244889.png?alt=media\&token=4e615e15-e7cc-4e69-a928-84ed794b4604)

### 暂时性死区

在了解暂时性死区前，先来看这一段有趣的代码

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709130638475.png)

因为`var`会进行变量提升，所以其实在提升时第二个`tmp`就覆盖了第一个`tmp`变量，最终输出都是`abc`。

但是我们把`var`换成`let`试一下

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709133559970.png)

会报错 `Uncaught ReferenceError: Cannot access 'tmp' before initialization`

因为在`if`块里面`let`声明的变量tmp，就相当于“绑定”了这个区域，不再受外部的影响。因此`tmp`不能在声明前被访问，哪怕是作用域链中有同名变量。这就是**暂时性死区**

了解之后，我们这样改一下就不会报错了

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709134226365.png)

### 总结

`let`是作为`var`的替代方法被设计的，因此它修复了`var`许多缺陷，而平时使用`let`应当牢记一个原则： ***先声明再使用***

## const

### 基础用法

`const`也是ES6新语法，用作声明一个常量。

同时`const`也和`let`一样，具有块级作用域、不能重复声明等特性。

因为const声明的是常量，因此应当在声明时就赋值（初始化），不能像`let`一样，先声明再赋值。

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709134902780.png)

被`const`声明的变量也无法再重新赋值.

### 本质

`const`本质上并不是保证值不变，而是保证变量指向的那个内存地址不变。

> 要理解这句话，你可能需要先理解什么是原始类型和引用类型
>
> 在《JavaScript高级程序设计第四版》有一节专门讲了原始类型和引用类型，推荐阅读

js里的变量本质上是指针，指向一块内存地址，对于**原始类型**来说，变量指向的那块内存保存的就是它的值，因此变量指向的内存地址不变，变量的值也就不会改变；

但是对于**引用类型**来说（Object），变量指向的内存地址，保存的只是一个指向实际数据的指针，因此`const`只能保证变量指向的地址是固定的（即总是指向另一个固定的地址），但是不能保证变量指向的那个指针的指向不变，因此也就不能保证引用类型的值不变。

![](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/image-20210709135928219.png)

## 参考

[网道—ES6](https://wangdoc.com/es6/let.html)

《JavaScript高级程序设计第四版》
