1、let和const命令特点

let命令特点:

  • 不存在变量提升。let声明的变量,只在let命令所在的代码块内有效。
  • 会出现暂时性死区。只要块级作用域内存在let命令,它所声明的变量就”绑定”(binding)这个区域,不再受外部的影响。
  • 不允许重复声明。let不允许在相同作用域内,重复声明同一个变量。因此,不能再函数内部重新声明参数。

const命令特点:

  • 声明一个只读的常量。一旦声明,常量的值就不能改变。且一旦声明变量,就必须立即初始化,不能留到以后赋值。
  • 作用域与let命令相同:只在声明所在的块级作用域内有效。
  • 也不存在变量提升。只能在声明的位置后面使用。
  • 也会出现暂时性死区。
  • 也不允许重复声明。

2、let

基本用法

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
for循环的计数器,就很适合使用let命令

1
2
3
4
for(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
2
3
4
5
6
7
for(let i = 0;i<3;i++){
let i = 'abc'
console.log(i)
//abc
//abc
//abc
}

上面代码正确运行,输出了三次abc。这表明函数内部的变量i与循环变量i在不同的一个作用域,有各自单独的作用域。

不存在变量提升

var 命令会发生”变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的
逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let命令改变了愈发行为,它所声明的变量一定要在声明后使用,否则报错。

1
2
3
4
console.log(foo)  //undefined
var foo = 2;
console.log(bar) //ReferenceError
let bar = 3

上面代码中,变量foo用var命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出
undefined。变量bar用let命令声明,不回发生变量提升。这表示在它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。


暂时性死区

只要块级作用域内存在let命令,它所声明的变量就是绑定(binding)这个区域,不再受外部的影响。

1
2
3
4
5
if (true){
tmp = 'abc';
let tmp;
console.log(tmp)
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明
前,对tmp赋值会报错。
ES6明确规定,如果区块中存在letconst命令,这个区块对这些命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。
凡事在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,成为”暂时性死区”(temporal dead zone,简称TDZ)

1
2
3
4
5
6
7
8
9
tmp = 'abc'
console.log(tmp); //ReferenceError

let tmp;
console.log(tmp); //undefined

let tmp;
tmp = 123;
console.log(tmp); //123

上面代码中,在let命令声明变量tmp之前,都属于变量tmp的”死区”。

“暂时性死区”也意味着typeof不再是一个百分百安全的操作。

1
2
typeof x;   //ReferenceError
let x;

上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的死区,只要用到该变量就会报错。因此,typeof运行时就会
抛出一个ReferenceError

作为比较,如果一个变量根本没有声明,使用typeof 反而不会报错。

1
typeof y;   //undefined

另外,下面的代码也会报错,与var行为不同。

1
2
3
4
5
//不报错
var x = x

//报错
let y = y

上面代码报错,也是因为暂时性死区。适应let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行
就属于这个情况,在变量x的声明语句还没有执行完成前就去取x的值,导致报错’x未定义’。
ES6 规定暂时性死区letconst语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在
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
7
function f(){
console.log(tmp);
if(false){
var tmp = 'hello'
}
}
f() //undefined

上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于
变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

第二种场景,用来计数的循环变量泄露为全局变量。

1
2
3
4
for (var i = 0;i<5;i++){
console.log(i)
}
console.log(i)//5

上面代码中,变量i只用来控制循环,但是循环技术后,它并没有消失,泄露成了全局变量。


ES6的块级作用域

let实际上为JavaScript新增了块级作用域。

1
2
3
4
5
6
7
8
function f1(){
let n = 5;
if(true){
let n = 10
}
console.log(n) //5
}
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
4
const 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
2
3
4
if(true){
const PI = 3.14
}
PI //Uncaught ReferenceError: PI is not defined

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

ES6声明变量的六种方法

ES5只有两种声明变量的方法:var命令和function命令。
ES6有六种声明变量的方法:var,function,let,const,import ,class



新数据类型:Symbol

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:undefinednull、布尔值 (Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡事属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

1
2
3
let s = Symbol()
typeof s
//"Symbol"

Promise

更详细的介绍Promise

Promise对象有以下两个特点。
(1)对象的状态不受外界印象,Promise对象代表一个异步操作,有三种状态:pending(进行中)、 fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是”承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会在变,任何时候都可以得到这个结果,Promise对象的状态该拜年,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就铃鼓了,不会再变了,会一直保持这个结果。这与事件(event)不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

1
2
3
4
5
6
7
const promise = new Promise(function(resolve,reject){
if (/*异步操作成功*/){
resolved(value)
}else{
reject(error);
}
});

generator

更详细的介绍[generator]

Generator函数是ES6提供的一种异步变成解决方案,语法行为与传统函数完全不同。Generator函数有多种理解角度。
语法上,首先可以把它理解成是一个状态机,封装了多个内部状态。

执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。

形式上,Generator函数是一个普通函数,但是有两个特征。意识,function关键字与函数名之间有一个星号*;二是,函数体内使用yield表达式,定义不同的内部变量(yield在英语里的意思就是”产出”)。

yield表达式是暂停执行的标记,而next方法可以恢复执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* helloGenerator(){
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
//{value:'hello',done:false}
hw.next()
//{value:'world',done:false}
hw.next()
//{value:'ending',done:true}
hw.next()
//{value: undefined,done:true}

总结一下,调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针,以后,每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,
yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。