ES 6 系列之Let 和 const 命令
let 命令
简单实用
ES6 中新增了 let 命令,用来声明变量。使用方式同 var 命令,但是 let 命令只在所在的代码块内有效。
1 | { |
在上面的使用案例中,我们可以看出 let 命令只在代码块中生效,而在代码块以外使用则无效。
for 循环中使用
我们来看看在 for 循环中使用 var 命令.
下面这段代码,打印出来的会是什么呢?可以跑一下代码,看一下控制台。
1 | for (var i = 0; i < 5; i++) { |
好了,你会发现,所有的打印都是 5,为什么呢?是因为 var 命令是在全局范围内有效,所以全局只有一个变量 i,每一次循环,变量 i 的值都会发生改变,所以当你打印是,打印的就是全局的 i ,也就是经过多次变化赋值的 5。
上面代码中,我在外面打印了一下 i ,来证明 var 定义的是一个全局变量。
来,让我们将 var 改成 let,现在打印的结果又是什么呢?
1 | for (let i = 0; i < 5; i++) { |
当然,我想很多人都猜到了结果,就是 0 1 2 3 4 ,那么这里又是为什么呢?
let 变量只在所在的代码块内有效,在子代码块中依然有效。在上述循环中 i 只在本轮循环内有效,所以每一次循环的 i 其实都是一个新定义的变量,所以会输出当前 i 的值,每次循环对应输出。
当然,这里可能会出现一个问题,每轮的变量都是一个新变量,那它是怎么知道上一轮循环的值的?稍做解释:JS 引擎(v8)内部会记住上一轮循环的值,初始化本轮的变量时,就在上一轮循环的基础上进行计算。
上面我们说,let 命令在子代码块中也有效,可以从 for 循环上来看,() 内部其实就是一个父级作用域,而 {} 内部就是一个单独的子作用域。
不存在变量提升
变量提升,即变量可以在声明之前使用,值为 undefined,var 命令存在,而 let 命令并没有变量提升,照样,我们来看看使用的案例。
1 | console.log(a); // undefined |
可以在控制台中跑一下这段代码,进行尝试。你会发现,a 变量进行了变量提升,而 b 变量没有发生,这表明了在声明之前使用,变量 b 是不存在的。
暂时性死区
代码块中,使用 let 命令声明变量之前,该变量都是不可用的。
在外部有一个全局变量 x,但是在内部使用 let 声明了一个 x,于是内部的 x 绑定了这个代码块,不受部影响。
但是在内部,声明变量之前的这个区域就是暂时性死区,我们来从下面的代码来看。
1 | var x = 100; |
暂时性死区的存在,也就意味着 typeof 的操作会变的不安全,在没有声明变量时,使用 typeof 并不会报错,返回一个 undefined,然而因为暂时性死区,会使得 typeof 报错 ReferenceError。
1 | if (true) { |
在函数默认值中也存在暂时性死区,而且会不容易发现。
1 | function add(x = y, y = 1) { |
在上面的代码中,就是如此,给函数赋默认值时,使用后面的值赋值给前面的 x,而这时候 y 还没有定义,所以会报错。
不允许重复声明
var 变量是允许进行重复声明的,但是 let 变量是不允许的。
来,上代码。
1 | var a = 10; |
const 命令
const 命令 声明一个只读的常量。一旦声明,常量的值就不能改变
基本用法
1 | const PI = 3.14159; |
const 声明时,必须立即进行初始化,不能进行赋值。
1 | cosnt PI; // 报错 SyntaxError |
const 只声明,不赋值就会报错。
const 作用域与 let 相同:只在声明所在的块级作用域内有效。
1 | { |
const 命令也不存在变量提升,但是存在暂时性死区,只能在声明后使用。
与 let 一样,不可进行重复声明。
本质
首先,我们来看一个对象的例子:
1 | const obj = {}; |
由上面可以看出,常量 obj 储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把 obj 指向另一个地址,但对象本身是可变的,所以依然可以添加新属性。
再来看一个数组的例子:
1 | const arr = []; |
同对象,常量 arr 是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给 arr,就会报错。
由此可见
const保证的 实际上并不是变量的值不改动,而是变量指向的那个内存地址所保存的数据不得改动。const只能保证这个指针式固定的,至于他指向的数据结构是不是可变的,就完全不能控制了。