ES6新特性
ES6新特性
文章参考:阮一峰老师的ECAMScript6入门
关键字
let关键字
与var相比:
- 不存在变量提升,即声明的变量一定要在声明后使用;“变量提升”可以在声明之前使用,值为undefined;
- 暂时性死区,在代码块内,使用let声明变量之前,该变量都是不可用的,把这种现象称为“暂时性死区”;
- 不允许重复声明,不允许在相同作用域内,重复声明一个变量;
- 块级作用域;全局作用域存在的问题:内层变量可能会覆盖外层变量;用来计数的循环变量泄露为全局变量;
const关键字
声明一个只读的常量,一旦声明,常量的值不能发生改变;
本质上来说,实际上并不是常量的值不能改变,而是指向的那个内存地址不能改变;对于简单数据类型来说,值就保存在变量所指向的那个内存地址中,因此就说值不能发生改变;但引用数据类型变量指向的是内存地址,保存的是一个指针,const只能保证这个指针是固定的。
解构赋值
变量的解构赋值
从数组和对象中提取值,对变量进行赋值,这种被称为解构;
数组的解构赋值
let a = 1;
let b = 2;
let c = 3;
let [a,b,c] = [1,2,3];
let [x,,y] = [1,2,3]
// x=1 y=3
对象的解构赋值
对象的解构与数组有一个重要的不同,数组的元素是按照次序排列的,变量的取值取决于它的位置;
而对象的属性是没有次序的,变量必须于属性同名;
let {foo, bar} = {foo:"aaa", bar:"bbb"}
foo//"aaa" bar:"bbb"
字符串的解构赋值
const [a,b,c,d,e] = 'hello'
//a "h" b "e" c "l" d "l" e "o"
数值和布尔值的解构赋值
解构赋值的规则:只要等号右边的值不是对象或者数组,就先将其转为对象。由于undefined和null无法转为对象,所以,对它们进行结构赋值都会报错。
let {toString:s} = 123;
s === Number.prototype.toString //true
函数参数的解构赋值
[[1,2],[3,4]].map(([a,b])=>a+b)
用途
- 交换变量的值
let x = 1;
let y = 2;
[x,y] = [y,x]
- 从函数返回多个值
//返回一个数组
function example(){
return [1,2,3]
}
let [a,b,c] = example()
- 函数参数的定义
function f([x,y,z]){...}
f([1,2,3])
- 提取JSON数据
let jsonData = {
id:42,
status:'ok',
data:[123,234]
}
let {id, status,data:number} = jsonData;
console.log(id,status,number)
// 42,"ok",[123,234]
- 遍历Map结构
任何部署了Iterator接口的对象,都可以使用for...of
循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值非常方便
const map = new Map();
map.set('first','hello')
map.set('second','world')
for(let [key,value] of map){
console.log(key+"is"+value)
}
// first is hello
// second is world
- 输入模块的指定方法
加载模块时,往往需要指定输入哪些方法,结构赋值使得语句清晰;
const {SourceMapConsumer, SourceNode} = require("source-map")
模板字符串
用反引号(`)标识;
模板字符串嵌入变量,需要将变量名写在${}
之中
箭头函数
箭头函数省去了function
关键字,采用=>
来定义函数;
与普通函数的区别:
- 从结构上来说,箭头函数更简洁
- 箭头函数没有自己的this,它会捕获自己在定义时所处的外层执行环境的this,所以它的this指向在它被定义的时候就已经确定了,之后不会改变;
- 无法使用
call()
、apply()
、bind()
等方法去改变this的指向; - 箭头函数不能作为构造函数使用;因为箭头函数里面没有
this
; - 箭头函数没有自己的
arguments
对象; - 不可以使用
yield
命令,因此箭头函数不能用作Generator
函数;
扩展运算符(…)
数组的扩展
Array.from()
数组实例的find()
、数组实例的includes()
对象的扩展
Object.assign()
基本数据类型Symbol
表示独一无二的值,通过Symbol
函数生成,可以保证不会与其他属性名冲突
Set和Map数据结构
基本用法
ES6提供了新的数据结构Set
,类似于数组,但成员的值是唯一的,没有重复的值;
set实例的属性和方法
add()
has()
delete()
clear()
遍历操作
eys()
返回键名的遍历器values()
返回键值的遍历器entries()
返回键值对的遍历器forEach()
使用回调函数遍历每个成员
Weakset
与set结构类似,也是不重复的值的集合,但是与Set有两个区别:
- WeakSet的成员只能是对象,而不能是其他类型的值
- WeakSet中的对象都是弱引用,即垃圾回收机制不考虑
WeakSet
对该对象的引用,也就是如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet
之中。
Map
ES6提供了Map数据结构,它类似于对象,也是键值对的集合。
WeakMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合;
区别:
- WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名;
- WaekMap的键名所指向的对象,不计入垃圾回收机制;
Promise对象
概念
- promise是异步编程的一种解决方案,较好的解决了回调地狱的问题,允许将回调函数嵌套改成链式调用;
回调函数本身没有什么问题,问题是多个回调函数的多层嵌套,代码朝横向发展,不易于维护;
多个异步操作形成了强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都需要跟着修改,这种情况就称为"回调地狱";
结构:
fs.readFile(fileA,'utf-8',function(err,data){
fs.readFile(fileB,'utf-8',function(err,data){
// ...
})
})
使用Promise后:
var readFile = require('fs-redfile-promise');
readFile(fileA)
.then(function (data){
console.log(data.toString())
})
.then(function(){
return readFile(fileB)
})
.then(function (data){
console.log(data.toString())
})
.catch(function(err){
console.log(err)
})
- 通过new Promise()的方法,创建一个Promise实例,Promise构造函数接收一个函数作为参数,这个函数有两个参数分别为
reject
和resolve
; - resolve函数的作用:将Promise的状态从“未完成”变成“成功”(即从pending变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject函数的作用:将Promise的状态从“未完成”变成“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去;
then()
方法:- Promise实例生成以后,可以用
then方法
分别指定resolved状态和rejected状态的回调函数; - then()方法可以接收两个回调函数作为参数,第一个回调函数是Promise对象的装填变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。这两个函数都接二手Promise对象传出的值作为参数。
- then方法返回的是一个新的Promise实例(注意:不是原来的那个Promise实例),因此可以采用链式法则,即then犯法后面再调用另一个then方法。
- Promise实例生成以后,可以用
状态
-
promise有三种状态:
pending
(进行中)、fulfilled
(已成功)、rejected
(已失败); -
Promise状态改变只有两种可能:从pending变为fulfilled和从pending变为rejected;
-
而且一单状态发生改变,就不会再变了。
方法
Promise实例的方法
- Promise.prototype.then()
- Promise.prototype.catch()
Promise
-
Promise.all()
-
const p = Promise.all([p1,p2,p3]) //其中p1 p2 p3都是Promise实例
-
p的状态,由p1,p2,p3决定
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled
- 只要p1、p2、p3中有一个被rejected,p的状态就会变成rejected,此时第一个被rejected的实例的返回值,会传递给p的回调函数
-
-
Promise.race()
-
const p = Promise.race([p1,p2,p3])
-
只要p1、p2、p3中有一个实例率先改变状态,p的状态就会跟着改变,那个率先改变的Promise实例返回值,就传递给p的回调函数
-
-
Promise.resolve()
-
Promise.reject()
-
Promise.try()
缺点
- 首先,无法取消promise,一旦创建会立即执行,无法中途取消;
- 其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;
- 第三,如果当前处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
Generator函数
概念
- ES6提供的一种异步编程解决方案;
- 最大的特点是可以交出函数的执行权(即暂停执行)
- Generator函数可以把它理解为一个状态机,封装了多个内部状态,执行该函数会返回一个遍历器对象,返回的遍历器对象可以依次遍历Generator函数内部的每一个状态;
function *helloWorldGenerator(){
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator()
//该函数有三个状态:hello、world和ending
特征
- function关键字与函数名之间有一个星号(*);
- 函数体内部使用yield表达式,定义不同的内部状态
调用过程
- 调用generator函数后,该函数并不执行,返回的也不是函数的运行结果,而是一个遍历器对象(Iterator Object)代表Generator函数的内部指针,只有调用next方法才会遍历下一个内部状态;
next()
- 每次调用next方法,内存指针就从函数头部或者上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。也就是说Generator是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行;
- 每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象,value属性表示当前的内部状态的值,是yield表达式后面的那个值;done属性是一个布尔值,表示是否遍历结束。
hw.next()
// {value:'hello',done:false}
hw.next()
// {value:'world',done:false}
hw.next()
// {value:undefined,done:true}
yield表达式
yield 表达式就是暂停标志;
遇到yield 表达式,就会暂停执行后面的操作,并将紧跟在yield 后面的那个表达式的值,作为返回的对象的属性值;
下一次调用next方法时,再继续往下执行,直到遇到下一个yield 表达式;
yield 与return的区别
**相同点:**都能返回紧跟在语句后面的那个表达式的值;
不同点:
-
每次遇到yield ,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能;
-
正常函数只能返回一个值,因为只能执行一次return;Generator函数可以返回一系列的值,因为可以有任意多个yield ;
Async函数
概念
Generator
的语法糖;将函数的星号(*)替换成async,将yield替换成await;
与Generator相比的改进:
- 内置执行器:Generator函数,需要调用next方法,才能真正执行,得到最后的结果;而async函数自带执行器;
- 更好的语义:aysnc和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果;
- 更广的适用性:yield命令后面只能是Thunk函数或者Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作);
- 返回的值是
Promise
:async函数的返回值是Promise对象,比Generator函数的返回值是Iterator对象方便多了,可以用then方法指定下一步操作;
基本用法
async函数返回一个Promise对象,可以使用then方法添加回调函数,当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体后面的语句;
async function getStockPriceByName(name){
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrices;
}
getStockPriceByName('goog').then(function(result){
console.log(result);
});
错误处理
如果await后面的异步操作出错,async函数返回的Promise对象被reject,会抛出一个错误的对象,导致catch方法的回调函数被调用,它的参数就是抛出的错误对象。
将函数放在try…catch代码块之中
async functin f(){
try{
await new Promise(function(resolve,reject){
throw new Error('出错了')
})
}catch(e){
}
return await('hello world')
}
如果有多个await命令。可以统一放在try…catch结构中。
for…of函数
for…of循环可以使用的范围包括数组:Set和Map结构、某些类似数组的对象(比如说arguments对象、DOM NodeList对象)、Generator对象,以及字符串。
JavaScript原有的for…in循环,只能获得对象的键名,不能直接获取键值。ES6提供for…of循环,允许遍历获得键值
var arr = ['a','b','c','d']
for(let a in arr){
console.log(a); // 0 1 2 3
}
for(let a in arr){
console.log(a); //a b c d
}
Class
定义类
ES6的class可以看做只是一个语法糖,它的绝大部分功能,ES5都可以做到
function point(x,y){
this.x = x;
this.y = y;
}
function.prototype.toString = function(){
return '('+this.x+','+this.y+')';
}
var p = new Point(1,2)
用class
改写
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
return '('+this.x+','+this.y+')';
}
}
事实上,类的所有方法都定义在类的prototype
属性上面;
在类的实例上面调用方法,其实就是调用原型上的方法;
class B{}
let b = new B()
b.constructor === B.prototype.constructor //true
constructor方法
constructor是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor方法会被默认添加。
class Point(){}
//等同于
class Point{
constructor(){}
}
类的实例对象
与ES5一样,实例的属性除非显示的定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
return '('+this.x+','+this.y+')';
}
}
var point = new Point();
point.toString() //(2,3)
point.hasOwnProperty('x') //true
point.hasOwnProperty('y') //true
point.hasOwnProperty('toString') //false
point.__proto__.hasOwnProperty('toString') //true
class的继承
class ColorPoint extends Point{
constructor(x,y,color){
super(x,y);
this.color = color;
}
toString(){
return this.color+' '+super.toString()//调用父类的toString()
}
}
super关键字
在子类的构造函数中,只有调用super之后,才能使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
super作为函数
super(),代表父类的构造函数,但是返回的子类B的实例,即super内部的this指的是B;
作为函数时,super()只能用在子类的构造函数之中,用在其他地方会报错;
class A{}
class B extend A{
constructor(){
super();
}
}
super作为对象
class A{
p(){
return 2;
}
}
class B extends A{
constructor(){
super();
console.log(super.p()); //2
}
}
上面代码中,子类B当中super.p()
,就是将super当作一个对象使用,这时,super在普通方法之中,指向A.prototype
,所以super.p()就相当于A.prototype.p();
注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。