文章

变量声明

var的缺陷

在说letconst前,我们先来看看var有哪些让人忍不住吐槽的地方。

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

![image-20210709122255627](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e7baf847-475c-4e17-a28a-27043ae8580f/image-20210709122255627.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T114442Z&X-Amz-Expires=86400&X-Amz-Signature=5772997b7c639fae13de9d1df39eb00626b93609a788ea7e4e1a9caac2bf158c&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709122255627.png"&x-id=GetObject))

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

这种宽泛的作用域对于iffor的语法不太友好,试想一下以下代码:

![image-20210709123419447.png (1354×590) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/27766886-166f-4ddb-9f2c-98612b81f407/image-20210709123419447.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T114758Z&X-Amz-Expires=86400&X-Amz-Signature=05c736603904e5a4ddae27996664f34bb24c6802c0dff0d285c3191dee8de391&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709123419447.png"&x-id=GetObject)

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

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

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

![image-20210709123924391.png (1354×660) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/0db5df63-166e-4a62-9315-29810f680fe3/image-20210709123924391.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T114831Z&X-Amz-Expires=86400&X-Amz-Signature=0f1be3e1f97bdb3e6906008b0ca9b5778b5c5e7be6e8f71166fec8c043499ee6&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709123924391.png"&x-id=GetObject)

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

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

![image-20210709122917767.png (1354×446) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/63c344be-bbab-48ec-8275-217cf107072e/image-20210709122917767.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T114856Z&X-Amz-Expires=86400&X-Amz-Signature=035b8ddd0a4af3cbe709b87d4ca1aaf48b2c0f039bb38f5beb25ed0934cc8219&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709122917767.png"&x-id=GetObject)

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

正是由于var这些反人类的设计,ES6之后出现了新的声明变量的方式letconst

let

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

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

请记住letconst的主要特性

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

什么是块级作用域

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

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

作用域我们都熟悉,那么什么是块呢?

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

![image-20210709125138700.png (1354×770) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e65363e0-84cd-4edc-b1f1-45c5c81764c1/image-20210709125138700.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T114911Z&X-Amz-Expires=86400&X-Amz-Signature=31cb0cbb3e21b62703b21c6576c9c61a89d702cc682ef1fb2787eb9c8bbb4624&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709125138700.png"&x-id=GetObject)

块级作用域

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

![image-20210709125540018.png (1354×554) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/5b7d5f9b-96d1-41ae-aa80-6c588e1b4266/image-20210709125540018.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T114913Z&X-Amz-Expires=86400&X-Amz-Signature=7f839f5366d75482491d04699ac7829e31d6884d90c6c63041fc13763470ee68&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709125540018.png"&x-id=GetObject)

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

不能重复声明

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

![image-20210709125906653.png (1354×518) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e5d2a481-e441-46fd-aca5-38a40a92b7dd/image-20210709125906653.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T114945Z&X-Amz-Expires=86400&X-Amz-Signature=5a5699da2000133017da45e5460fe2d76a552cbbde2ff87fbb36ff75f7f4c36e&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709125906653.png"&x-id=GetObject)

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

不存在变量提升

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

![image-20210709130244889.png (1354×624) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/ddeff5da-3123-400e-a86c-25d03c91c4c8/image-20210709130244889.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T114948Z&X-Amz-Expires=86400&X-Amz-Signature=e34387fc2e8c9e68ed5befec0f4e0faff34b1b14cfd734e500307ff852d0c610&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709130244889.png"&x-id=GetObject)

暂时性死区

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

![image-20210709130638475.png (1354×660) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/22d02067-2797-4b22-ad2e-2c1478437d77/image-20210709130638475.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T115011Z&X-Amz-Expires=86400&X-Amz-Signature=abc73e78ef4a5ee9b6d82ff30e1197ee8d32e39d25c1618ce262b09c97e5f5d4&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709130638475.png"&x-id=GetObject)

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

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

![image-20210709133559970.png (1354×660) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/d963c38d-3134-4c86-91a9-fa5ad9115131/image-20210709133559970.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T115013Z&X-Amz-Expires=86400&X-Amz-Signature=f72a26c2c9c3e86c8ad371de3d1effdd150a5c6a2897bd274225bc674447cacf&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709133559970.png"&x-id=GetObject)

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

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

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

![image-20210709134226365.png (1354×660) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/9a96fe3d-1085-4ec4-aea6-0f7922f7cc06/image-20210709134226365.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T115110Z&X-Amz-Expires=86400&X-Amz-Signature=2623fcaaf5b6963a40a5192911e6106f4bac753947ed2ce456c8d46697fa94c5&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709134226365.png"&x-id=GetObject)

总结

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

const

基础用法

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

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

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

![image-20210709134902780.png (1354×446) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/8c95b313-f477-4af3-bb92-e39eb4a5c2cf/image-20210709134902780.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T115124Z&X-Amz-Expires=86400&X-Amz-Signature=765009c46a044a4a2237818ca2cb43facdf90b8dc1ebeea97be189ec684b90cf&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709134902780.png"&x-id=GetObject)

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

本质

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

要理解这句话,你可能需要先理解什么是原始类型和引用类型

在《JavaScript高级程序设计第四版》有一节专门讲了原始类型和引用类型,推荐阅读

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

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

![image-20210709135928219.png (1354×770) (amazonaws.com)](https://s3.us-west-2.amazonaws.com/secure.notion-static.com/4fd904c4-9402-4acb-9104-7545085aab6a/image-20210709135928219.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220524%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220524T115126Z&X-Amz-Expires=86400&X-Amz-Signature=d39c7a5822a5e7aa27968f777554c6c83abd01808aa72625020b5d34e68dbb38&X-Amz-SignedHeaders=host&response-content-disposition=filename %3D"image-20210709135928219.png"&x-id=GetObject)

参考

部分内容参考自网道—ES6