咱们知道JS这门语言有一套规定了它的基本语法的规范:ECMAscript。以前开发人员使用的都是ES5规范,后来,在2015年6月,ES6终于脱离了草案阶段,正式发布。在JS的ES5(ECMAscript5)规范中,变量的声明就是var
和function
这两种,而这两种变量声明方式存在着一些不太合乎常理的问题。好比说很典型的一个问题:变量提高。以上两种方式声明变量都会把这个变量提高到当前做用域的最上面(这里不详细说细节),这其实并不合理,由于咱们声明变量的目的就是去使用它,那么咱们天然是想用它的时候去声明,而后使用。还有一种不太好的地方:for
循环里面定义的计数变量是会泄露出去的,而咱们写for
循环实际上只想让这个计数变量在当前的for
循环内部使用。为了解决这一系列的问题,在ES6中,新增了let
、const
声明方式,固然也还有一些其余的声明方法先不提,咱们今天只说let
、const
。javascript
let
的使用和var
同样。java
let a = 0
复制代码
而后就能够访问这个变量函数
console.log(a) // 0
复制代码
let
声明变量和var
声明变量有所区别:学习
ES5声明变量:ui
console.log(a) // undefined,存在变量提高
var a = 0
复制代码
用let
的:编码
console.log(a) // a is not defined,不存在变量提高
let a = 0
复制代码
由于var
存在变量提高,打印undefined,而let
不存在变量提高,直接就会报错。spa
在ES5里面没有块级做用域的概念,咱们多经过当即执行函数来模仿一块块做用域,把这一块的变量保护起来,同时也防止污染到全局。在ES6里面,加入了块级做用域的概念,就是说let
、const
声明变量的时候也会把它们绑定到当前的代码块中,这个块咱们称之为这个变量的块级做用域,外部不可访问,就相似于ES5里面的函数内声明的变量。指针
ES5:code
if (true) {
var a = 0
console.log(a) // 0
}
console.log(a) // 0
复制代码
这里面在代码块if里面声明的变量a
做用域是全局,因此均可访问。对象
ES6:
if (true) {
let a = 0
console.log(a) // 0
}
console.log(a) // a is not defined
复制代码
在这里面,let
声明的变量a只在这个代码块内有效,外部不可访问,因此会报错。这就是块级做用域。这个块级做用域有许多有意思的地方。请看下面的代码:
function fn () {
var a = 0
if (true) {
var a = 1
console.log(a) // 1
}
console.log(a) // 1
}
复制代码
在ES5里面,这样写的话,就至关于在if代码块里面后定义的变量a覆盖了前面定义的变量a,因此输出的a的值都是1。那要是用ES6的let
声明呢?
function fn () {
let a = 0
if (true) {
let a = 1
console.log(a) // 1
}
console.log(a) // 0
}
复制代码
在这里由于let
声明的变量只在当前的块级做用域内有效,因此,在if代码块里面打印a就是1,在外层打印a就是外层块做用域a的值0,也就是说在这里,父块级做用域里面的变量a用本身在当前块级做用域内的值,子块级做用域的变量a用本身当前块做用域内的值,它们是互不影响的。那么能够引伸出一个let
命令的很好的应用场景:for
循环。
ES5里面的for
循环:
var a = []
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[2]() // 10
复制代码
这里面会打印出来10,由于定义在for
循环内部的计数变量i
其实会泄露为全局变量,因此咱们在执行最后一段代码时,访问到的i
就是全局的惟一的这个i
,那么这个i
就会随着循环改变,可是始终是这个惟一的i
。这个时候for
循环已经结束,i
值变为了10,因此咱们访问哪一个都只会获得10,可是这显然不是咱们想要的,因此let
来了。
var a = []
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i)
}
}
a[2]() // 2
复制代码
这里面就很特殊,let
声明打这个i
只在for
循环体内部可用,外部不能访问,因此,这里每一次循环的时候都访问的是当前循环的i
,就是说,每一次进入新的循环的时候,i
都是一个新的变量i
,不是以前的变量i
,变量i
只在本身当前的循环里面有效,只是在JS的引擎里面会记录i
值,后面的新的变量i
就会从这个值开始继续循环。因此,咱们这里打印的i
值就是2,由于它只能访问它做用域里面的那个变量i
。
for
循环里面还有一点要注意的就是:设置计数变量的部分是一个父做用域,循环体内部是一个子做用域。像下面这样:
for (let i = 0; i < 5; i++) {
let i = '我是子做用域'
console.log(i)
}
// 我是子做用域
// 我是子做用域
// 我是子做用域
// 我是子做用域
// 我是子做用域
复制代码
这里面打印了五次我是子做用域,也就是说,设置计数变量的部分是一个父做用域,循环体内部是一个子做用域,他们是互不影响的,这个我前面有说过,各自用本身的块做用域里面的变量的值,就是说:计数变量的i
就负责控制循环,循环体里面的i
就每次都打印一下就能够了,各司其职。外面的块级做用域不会影响到内层的块级做用域,内层的块级做用域也不会影响外面的块级做用域,各自都是一个封闭的小区域。这里面我总会想到CSS里面的BFC区域(若是不了解BFC自行忽略这句话,没什么影响),和那个差很少。关于块级做用域差很少就这些,咱们继续说下面的内容。
ES6明确规定,若是区块中存在let
,const
命令,它所声明的变量就会绑定到当前的块级做用域上,不受外面的影响,这我前面也有说过,那么这个块级做用域就会造成封闭做用域,只要在声明以前使用,就会报错,这就是暂时性死区(TDZ)。最好理解的就是看下面的代码:
if (true) {
a = 2 // a is not defined
console.log(a) // a is not defined
let a
console.log(a) // undefined
a = 0
console.log(a) // 0
}
复制代码
按照咱们以前所说,只要存在let
,那么这个变量a
就是当前块级做用域内有效,就造成封闭做用域了,因此咱们在声明以前尝试去给它赋值,打印它,都会报错,由于咱们不能够在声明以前使用他们它,因此在这里,在声明前面的部分就是它的暂时性死区。
某些隐蔽的暂时性死区:
function test (a = b, b = 0) {
return [a, b]
}
test() // b is not defined
复制代码
报错的缘由:在变量b
声明以前尝试去使用它,这属于b
的死区,因此报错。
let a = a // a is not defined
复制代码
咱们知道,赋值运算符先算右边的内容,那么这里在声明a
以前尝试获取它,一样属于a
的死区,报错。
let
不容许在相同做用域内重复声明同一个变量。注意:是相同做用域内。
let a = 1
let a = 1 // Uncaught SyntaxError: Identifier 'a' has already been declared
复制代码
函数内部也不能够从新声明参数:
function fn (arg) {
let arg
}
fn() // Uncaught SyntaxError: Identifier 'arg' has already been declared
复制代码
可是这样作是能够的:
function fn (arg) {
{
let arg
}
}
fn()
复制代码
由于,这时声明的变量就绑定到了当前块级做用域上,不影响到外部,因此它和函数参数的那个变量的做用域也不同了,是被容许的。
const
使用方法和let
基本一致。再也不赘述。
前面的特性都和let
同样,这里我只说区别。
const
声明的是一个只读的常量,一旦声明,常量的值或者指向的地址不得改变。
const a = 1
a = 2 // Uncaught TypeError: Assignment to constant variable.
复制代码
const
因为这个特性,因此它声明常量的同时必须马上初始化(赋值),不然报错。
const a // Uncaught SyntaxError: Missing initializer in const declaration
复制代码
实际上const
声明常量的不可变是分状况的,若是是简单数据类型的,则值不可变,若是是复杂类型的,则指针不可变,实际对象的属性和方法可变。解释一下,简单数据类型直接把值存储在栈内存当中,因此这个值就不可再改变,复杂类型的数据在栈内存中只存储了一个地址(指针),这个地址不可变,可是这个指向堆内存的地址指向的具体的对象是能够改变的。好比这样:
const obj = {}
obj.name = 'reslicma'
console.log(obj) // {name: "reslicma"}
复制代码
咱们只须要知道,const
声明的常量保存在栈内存的值都不能改变,可是复杂型数据指向堆内存中具体的对象能够改变就能够了,至于这个栈和堆内存具体内容能够看一下我上一篇文章,里面很详细的叙述了这两者和区别和联系(硬核推荐。。。)。并且ES6学习的话,真的很推荐阮老师的《ES6标准入门》这本书。。。
不是对知识点总结,是说一说ES6引入let
、const
的做用:之前使用var
由于存在变量提高,会形成一些没必要要的错误,或者一些意料以外的咱们不想要的错误,因此加入了let
、const
避免这类错误,同时也是为了但愿开发人员们养成良好的的编码规范和编码风格。