ES6语法(1)——let和const
1、let和const命令特点
let命令特点:
- 不存在变量提升。
let
声明的变量,只在let命令所在的代码块内有效。 - 会出现暂时性死区。只要块级作用域内存在
let
命令,它所声明的变量就”绑定”(binding)这个区域,不再受外部的影响。 - 不允许重复声明。
let
不允许在相同作用域内,重复声明同一个变量。因此,不能再函数内部重新声明参数。
const命令特点:
- 声明一个只读的常量。一旦声明,常量的值就不能改变。且一旦声明变量,就必须立即初始化,不能留到以后赋值。
- 作用域与
let
命令相同:只在声明所在的块级作用域内有效。 - 也不存在变量提升。只能在声明的位置后面使用。
- 也会出现暂时性死区。
- 也不允许重复声明。
2、let
基本用法
ES6 新增了let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效。for
循环的计数器,就很适合使用let
命令1
2
3
4for(var i = 0;i<3;i++){}
for(let j = 0;j<3;j++){}
console.log(i) //3
console.log(j) //RefernceError
上面代码中,使用var
声明的计数器i
,在全局范围内都有效,最后输出是3
。而使用let声明的计数器j
只在for
循环体内有效,在循环体外引用就会报错。
此外,for
循环还有一个特别指出,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
1 | for(let i = 0;i<3;i++){ |
上面代码正确运行,输出了三次abc
。这表明函数内部的变量i
与循环变量i
在不同的一个作用域,有各自单独的作用域。
不存在变量提升
var 命令会发生”变量提升”现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的
逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let
命令改变了愈发行为,它所声明的变量一定要在声明后使用,否则报错。
1 | console.log(foo) //undefined |
上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined
。变量bar用let命令声明,不回发生变量提升。这表示在它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就是绑定(binding)这个区域,不再受外部的影响。1
2
3
4
5if (true){
tmp = 'abc';
let tmp;
console.log(tmp)
}
上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明
前,对tmp
赋值会报错。
ES6明确规定,如果区块中存在let
和const
命令,这个区块对这些命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。
凡事在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,成为”暂时性死区”(temporal dead zone,简称TDZ)
1 | tmp = 'abc' |
上面代码中,在let
命令声明变量tmp
之前,都属于变量tmp
的”死区”。
“暂时性死区”也意味着typeof
不再是一个百分百安全的操作。
1 | typeof x; //ReferenceError |
上面代码中,变量x
使用let
命令声明,所以在声明之前,都属于x的死区,只要用到该变量就会报错。因此,typeof运行时就会
抛出一个ReferenceError
作为比较,如果一个变量根本没有声明,使用typeof
反而不会报错。1
typeof y; //undefined
另外,下面的代码也会报错,与var行为不同。
1 | //不报错 |
上面代码报错,也是因为暂时性死区。适应let
声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行
就属于这个情况,在变量x的声明语句还没有执行完成前就去取x的值,导致报错’x未定义’。ES6
规定暂时性死区let
、const
语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在ES5
是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质是,只要已进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到变量声明的哪一行代码出现,才可以获取和使用该变量。
不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//不报错
function func(){
var a = 10;
var a = 1;
}
//报错
function func(){
let a = 10;
var a = 1;
}
//报错
function func(){
let b = 10;
let b = 1;
}
因此,不能在函数内部重新声明变量参数。
3、块级作用域
为什么需要块级作用域?
ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
第一种场景,内层变量可能会覆盖外层变量。1
2
3
4
5
6
7function f(){
console.log(tmp);
if(false){
var tmp = 'hello'
}
}
f() //undefined
上面代码的原意是,if
代码块的外部使用外层的tmp变量,内部使用内层的tmp
变量。但是,函数f
执行后,输出结果为undefined
,原因在于
变量提升,导致内层的tmp
变量覆盖了外层的tmp变量。
第二种场景,用来计数的循环变量泄露为全局变量。1
2
3
4for (var i = 0;i<5;i++){
console.log(i)
}
console.log(i)//5
上面代码中,变量i
只用来控制循环,但是循环技术后,它并没有消失,泄露成了全局变量。
ES6的块级作用域
let实际上为JavaScript新增了块级作用域。
1 | function f1(){ |
上面的函数有两个代码块,都声明了变量n,运行后输出5。这表示外层代码块不受内层代码块的影响。如果两次都使用
var定义变量n,最后输出的值才是10。
ES6 允许块级作用域的任意嵌套。块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。1
2
3
4
5
6
7
8
9
10//IIFE写法
(function(){
var tmp = 3
console.log(tmp)//3
}())
//块级作用域写法
{
let tmp = 5
console.log(tmp)//5
}
块级作用域与函数声明
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
ES6引入了块级作用域,明确允许在块级作用域之中声明函数。ES6规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
4、const
基本用法
const声明一个只读的敞亮。一旦声明,常量的值就不能改变。1
2
3
4const PI = 3.14
console.log(PI)
PI = 3 //TypeError
上面的代码表明改变常量的值会报错。
const声明的变量不能改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。1
const PI //Uncaught SyntaxError: Missing initializer in const declaration
上面代码表示,对于const来说,只声明不复制,就会报错。
const的作用域与let命令相同:还在声明所在的块级作用域内有效。
1 | if(true){ |
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
ES6声明变量的六种方法
ES5只有两种声明变量的方法:var
命令和function
命令。
ES6有六种声明变量的方法:var
,function
,let
,const
,import
,class
新数据类型:Symbol
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:undefined
、null
、布尔值 (Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡事属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
1 | let s = Symbol() |
Promise
更详细的介绍Promise
Promise对象有以下两个特点。
(1)对象的状态不受外界印象,Promise对象代表一个异步操作,有三种状态:pending
(进行中)、 fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是”承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会在变,任何时候都可以得到这个结果,Promise对象的状态该拜年,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就铃鼓了,不会再变了,会一直保持这个结果。这与事件(event)不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
1 | const promise = new Promise(function(resolve,reject){ |
generator
更详细的介绍[generator]
Generator函数是ES6提供的一种异步变成解决方案,语法行为与传统函数完全不同。Generator函数有多种理解角度。
语法上,首先可以把它理解成是一个状态机,封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。
形式上,Generator函数是一个普通函数,但是有两个特征。意识,function关键字与函数名之间有一个星号*
;二是,函数体内使用yield
表达式,定义不同的内部变量(yield
在英语里的意思就是”产出”)。
yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
1 | function* helloGenerator(){ |
总结一下,调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针,以后,每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,
是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。