这是我参与更文挑战的第1天,活动详情查看:更文挑战
本文会介绍
let
和const
基本用法和基本特性,如已掌握可直接拉到4.x
ES6新增了
let
和const
命令来声明变量,下面我们先来了解一下它们的一些特性。
1. let命令
1.1 基本用法
ES6新增了let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效。
{
let testA = 'A'
var testB = 'B'
}
console.log(testB) // 'B'
console.log(testA) // testA is not defined
复制代码
以上代码中,var
声明的变量返回正确值,let
声明的变量is not defined
。说明let
声明的变量只在它所在的代码块生效。
1.2 不存在变量提升
-
var
命令会发生变量提升(本文不详细介绍变量提升,感兴趣的可以看冴羽大佬的github),即变量可以在声明之前使用,值为undefined
。按正常逻辑,变量应该在声明语句之后才可以使用。 -
let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
console.log(wave); // undefined
var wave = 'wave'
console.log(sky) // ReferenceError: Cannot access 'sky' before initialization
let sky = 'sky'
复制代码
1.3 暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就绑定
(如何绑定我们后面会详细说到)了这个区域,不再受外部影响。
var tmp = 123
if(true) {
tmp = '123'
let tmp
}
复制代码
以上代码中,全局存在tmp
变量,块级作用域中let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以let
声明变量前,对tmp
赋值会报错。
ES6明确规定,如果块区中存在let
或者const
命令,这个块区对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
或者const
命令声明变量之前,该变量都是不可用的。这在语法上称为暂时性死区
。主要是为了减少运行时的错误,防止变量声明前就使用这个变量,从而导致不可预料的问题。
1.4 不可重复声明
重复声明会报错
2. const命令
2.1 基本用法
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
复制代码
const
声明的变量不得改变值,意味着就必须初始化,不能留到以后赋值。
const foo;
// SyntaxError: Missing initializer in const declaration
复制代码
const
与let
一样也是具有块级作用域
的特性,并且声明变量也不提升,同样存在暂时性死区,只能在声明位置后面使用。
2.2 本质
const
实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得变动。对于简单类型数据,值就保存在变量指向的那个内存地址。而对于引用类型,变量指向的内存地址,保存的只是一个指向数据的指针,const
只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就控制不了了。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
复制代码
3. 注意
ES6的块级作用域必须有大括号,如果没有大括号,JavaScript引擎就认为不存在块级作用域。
// 第一种写法,报错
if (true) let x = 1;
// 第二种写法,不报错
if (true) {
let x = 1;
}
复制代码
4. 块级作用域原理
前面在介绍
const
和let
的时候,都提到了一个词块级作用域
,下面我们来详细谈谈块级作用域是什么,原理是什么
4.1 首先我们得先从作用域讲起
作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗的讲就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和声明周期
在ES6之前,只有两种作用域:全局作用域和函数作用域
没有块级作用域会导致很多问题:
- 变量覆盖
- 命名冲突
- 变量提升
- 内存泄漏
4.2 块级作用域是如何实现的?
现在我们已经知道了可以通过
let
或者const
来声明块级作用域的变量,那么在一段代码中ES6是如何做到既支持变量提升,又支持块级作用域的呢?
我们需要站在执行上下文的角度来揭晓答案。
JavaScript 引擎是通过变量环境实现函数级作用域的,那么 ES6 又是如何在函数级作用域的基础之上,实现对块级作用域的支持呢?你可以先看下面这段代码:
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
复制代码
当执行上面这段代码的时候,JavaScript 引擎会先对其进行编译并创建执行上下文,然后再按照顺序执行代码,关于如何创建执行上下文我们在前面的文章中已经分析过了,但是现在的情况有点不一样,我们引入了 let 关键字,let 关键字会创建块级作用域,那么 let 关键字是如何影响执行上下文的呢?
接下来我们就来一步步分析上面这段代码执行流程
第一步是编译并创建执行上下文,你可以参考下图:
通过查看上图,我们可以得出结论:
- 通过
var
声明的变量,在编译阶段被放在变量环境中了 - 通过
let
声明的变量,在编译过程中会被放到词法环境中 - 在函数的作用域块内部,通过 let 声明的变量并没有被存放到词法环境中
继续执行:
从图中可以看出,当进入函数的作用域块时,作用域块中通过let
声明的变量,会被放在词法环境的一个单独区域中,这个区域中的变量并不影响作用域块外面的变量,比如在作用域外面声明了变量 b,在该作用域块内部也声明了变量 b,当执行到作用域内部时,它们都是独立的存在。
其实,在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。需要注意下,我这里所讲的变量是指通过 let 或者 const 声明的变量。
参考文献:
-
ECMAScript 6 入门 阮一峰
-
李兵 - 块级作用域:var缺陷以及为什么要引入let和const?