基本用法
ES6 引入了 let
命令,用来声明变量。它的用法和 var
类似,但 let
声明的变量只在它所在的代码块内有效。
举个例子,如果在代码块里分别用 let
和 var
声明两个变量,然后在代码块外调用它们,你会发现 let
声明的变量会报错,而 var
声明的变量却能正常返回值。这说明 let
声明的变量只在它所在的代码块内有效。
for 循环中的 let
for
循环的计数器非常适合用 let
来声明。比如,计数器 i
只在 for
循环体内有效,如果在循环体外引用它,就会报错。
如果使用 var
声明计数器,最后输出的会是 10。这是因为 var
声明的变量在全局范围内都有效,每次循环都会改变 i
的值,导致所有循环内的函数都指向同一个 i
,最终输出最后一轮的值。
而如果用 let
,变量仅在块级作用域内有效,最后输出的会是 6。这是因为每一轮循环的 i
都是一个新的变量,JavaScript 引擎会记住上一轮的值,并在本轮初始化时基于它进行计算。
块级作用域的父子关系
for
循环还有一个特别之处:设置循环变量的部分是父作用域,而循环体内部是一个单独的子作用域。这意味着函数内部的变量 i
和循环变量 i
不在同一个作用域,各自。
不存在变量提升
var
命令会发生“变量提升”现象,即变量可以在声明之前使用,值为 undefined
。这种行为有些奇怪,因为按照常理,变量应该在声明后才能使用。
let
纠正了这个问题,它声明的变量必须在声明后使用,否则会报错。比如,用 var
声明的变量 foo
会发生变量提升,脚本运行时 foo
已经存在但没有值,所以输出 undefined
。而用 let
声明的变量 bar
不会提升,声明前使用会报错。
暂时性死区
只要块级作用域内存在 let
命令,它所声明的变量就“绑定”在这个区域,不受外部影响。比如,全局变量 tmp
和块级作用域内 let
声明的局部变量 tmp
冲突,导致在 let
声明前对 tmp
赋值会报错。
ES6 明确规定,如果区块中存在 let
和 const
命令,这个区块对这些变量从一开始就形成了封闭作用域。在声明前使用这些变量会报错,这种现象称为“暂时性死区”(TDZ)。
typeof 不再安全
“暂时性死区”还意味着 typeof
不再是一个百分之百安全的操作。比如,变量 x
用 let
声明,在声明前使用 typeof
会报错。而如果一个变量根本没有被声明,typeof
反而不会报错。
不允许重复声明
let
不允许在相同作用域内重复声明同一个变量。因此,不能在函数内部重新声明参数。
为什么需要块级作用域?
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来了一些不合理的情况。
第一种情况是内层变量可能会覆盖外层变量。比如,if
代码块外使用外层变量 tmp
,内部使用内层变量 tmp
,但由于变量提升,内层变量会覆盖外层变量,导致输出 undefined
。
第二种情况是循环变量泄露为全局变量。比如,变量 i
只用来控制循环,但循环结束后,它并没有消失,泄露成了全局变量。
ES6 的块级作用域
let
实际上为 JavaScript 新增了块级作用域。比如,函数有两个代码块,都声明了变量 n
,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都用 var
定义变量 n
,最后输出的值才是 10。
ES6 允许块级作用域任意嵌套,外层作用域无法读取内层作用域的变量,但内层作用域可以定义外层作用域的同名变量。
块级作用域与函数声明
ES5 规定函数只能在顶层作用域和函数作用域中声明,不能在块级作用域中声明。但浏览器为了兼容旧代码,仍然支持在块级作用域中声明函数。
ES6 引入了块级作用域,明确允许在块级作用域中声明函数。块级作用域内声明的函数类似于 let
,在块级作用域外不可引用。
do 表达式
块级作用域本质上是一个语句,将多个操作封装在一起,没有返回值。现在有一个提案,使得块级作用域可以变为表达式,办法就是在块级作用域前加上 do
,使它变为 do
表达式。
const 的基本用法
const
声明一个只读的常量,一旦声明,常量的值就不能改变。const
声明的变量不得改变值,这意味着声明时必须立即初始化,不能留到以后赋值。
const
的作用域与 let
相同,只在声明所在的块级作用域内有效。const
声明的常量也不提升,存在暂时性死区,只能在声明后使用。
const 的本质
const
实际上保证的是变量指向的内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在内存地址中,因此等同于常量。但对于复合类型的数据(对象和数组),const
只能保证指针固定,数据结构本身是可变的。
ES6 声明变量的 6 种方法
ES5 只有两种声明变量的方法:var
和 function
。ES6 新增了 let
、const
、import
和 class
,一共有 6 种声明变量的方法。
顶层对象
ES5 中,顶层对象的属性与全局变量等价。ES6 规定,var
和 function
声明的全局变量依旧是顶层对象的属性,而 let
、const
、class
声明的全局变量不属于顶层对象的属性。