本文将总结一ECMAScript 6-8 的常用特性,并使用一些案例来展示特性的使用场景,学以致用
let
在ES6之前使用var
关键字来声明变量,会出现重复声明导致变量被覆盖却不会报错的问题,且使用var
声明的变量会被提升到最前进行解析,使用function声明的函数次之
1 | var camper = 'James'; |
与var
不同的是,当使用ES6新增的let
来声明变量时,没有变量提升,且同一名字的变量只能被声明一次,
1 | let camper = 'James'; |
let 声明的变量在{} (花括号)中参数局部作用域,作业是不会污染其他作用域,不会影响作用域链
1 | if(true){ |
Const
const
为 英文 constant
的简写,其意为常量
使用const
声明的常量拥有以下规则:
- 常量一定要赋初值
- 与let一样产生块级作用域
- 常量名称一般为全大写字母
- 常量值不能修改,对象或者数组类型可修改内部数据,这意味着使用
const
声明的引用常量在堆内存中的地址不能发生变化
解构赋值
数组解构
在 ES6 里面,解构数组可以如同解构对象一样简单。与数组解构不同,数组的扩展运算会将数组里的所有内容分解成一个由逗号分隔的列表。所以,你不能选择哪个元素来给变量赋值,而对数组进行解构却可以让我们做到这一点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const [a, b] = [1, 2, 3, 4, 5, 6];
console.log(a, b); // 1, 2
//变量a以及b分别被数组的第一、第二个元素赋值。
//我们甚至能在数组解构中使用逗号分隔符,来获取任意一个想要的值:
const [a, b,,, c] = [1, 2, 3, 4, 5, 6];
console.log(a, b, c); // 1, 2, 5
// 不通过第三个变量交换二者的值
let a = 8, b = 6;
[a,b] = [b,a]
console.log(a); // 应该等于 6
console.log(b); // 应该等于 8对象解构
1
2
3
4
5
6
7
8const obj = {
name:'ls',
age:20,
getName:function(){
return this.name;
}
}
const {name,age,getName} = obj用于函数传参,这里也使用了ES6的新特性,参数默认值
1
2
3
4
5
6
7
8
9
10function connent({host="",username="初始值",password,port}){
console.log(host,username,password,port)
}
connect({
host:'',
username:'',
password:'',
port:''
})在某些情况下,你可以在函数的参数里直接解构对象,这样的操作去除了多余的代码,使代码更加整洁
1
2
3
4
5
6
7
8
9const profileUpdate = (profileData) => {
const { name, age, nationality, location } = profileData;
}
//上面的操作解构了传给函数的对象。这样的操作也可以直接在参数里完成
const profileUpdate = ({ name, age, nationality, location }) => {
/* 对这些参数执行某些操作 */
}这样做还有个额外的好处:函数不需要再去操作整个对象,而仅仅是操作复制到函数作用域内部的参数。
1
2
3
4
5
6const a = {
start: { x: 5, y: 6},
end: { x: 6, y: -9 }
};
const { start : { x: startX, y: startY }} = a;
console.log(startX, startY); // 5, 6在上面的例子里,
a.start
将值赋给了变量start
,start
同样也是个对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//使用解构赋值来得到forecast.tomorrow的max,并将其赋值给maxOfTomorrow。
const LOCAL_FORECAST = {
today: { min: 72, max: 83 },
tomorrow: { min: 73.3, max: 84.6 }
};
function getMaxOfTmrw(forecast) {
"use strict";
// 在这行以下修改代码
const {tomorrow:{max:maxOfTomorrow}} = forecast; // 改变这一行
// 在这行以上修改代码
return maxOfTomorrow;
}
console.log(getMaxOfTmrw(LOCAL_FORECAST)); // 应该为 84.6
简化对象写法
如果在对象内赋值中发现属性名和属性值变量名称相同,则直接使用属性名即可
1 | let a = 'apple' |
箭头函数
1 | const arrowFunc = () => { |
- 箭头函数内部的this始终指向在声明时的作用域中的this,且
this
不可被call()`` bind()`` apply()
更改,通常应用于计时器(因为计时器中的this指向的是windows) 以及 定义类中的函数 - 内部没有arguments伪数组,意味着不能使用
arguments.callee()
方法进行自调用 - 当内部代码只有return语句时候,return可省略,使用()包裹返回的内容,但执行的代码使用{} 包裹
rest参数
ES6 在函数中引入 rest参数, 用于获取函数实参,代替arguments
1 | function plus(...args){ |
Class
ES6 提供了一个新的创建对象的语法,使用关键字class
。
值得注意的是,class
只是一个语法糖,它并不像 Java、Python 这一类的语言一样,严格履行了面向对象的开发规范。
ES6 之前由原型链来进行对象的继承于重用,在构造函数的显式原型上添加公共方法,使用new 来实例化对象,
1 | var SpaceShuttle = function(targetPlanet){ |
class
的语法只是简单地替换了构造函数的写法:
1 | class SpaceShuttle { |
注意class
关键字声明了一个新的函数,并在其中添加了一个会在使用new
关键字创建新对象时调用的构造函数。
继承
在ES5 中,我们常常用到组合继承来实现父子间的继承,即使用原型链继承的方式继承父类的方法,使用借用构造函数的方式继承父级的属性,最后再将子类原型的构造器指向自身
1 | function Phone(brand,price){ |
以上继承方法存在一个问题,即创建一个对象需要调用2次父类构造函数,第一次为子类构造函数中借用构造属性时,第二次为子类使用原型继承父类构造函数的方法时
使用寄生组合式继承可以解决这个问题,这也是ES6的class继承实现原理,与上方的构造的区别就是:
- 组合式继承先使用借用函数构造了子类实例的属性,然后再通过改变子类的原型执行父类实例的方式继承父类的方法,调用2次父类构造函数
- 寄生组合继承,先使用函数将子类的原型指向了父类的实例,而父类的实例是通过将父类的原型交给一个新函数的原型构造出的,没有调用父类构造器来构造,此后,在new子类构造函数的时候,才调用父类构造函数来构造子类属性,过程中只调用一次父类构造函数
1 | function createObject(prototype){ |
ES6中的使用类继承,super()
方法只能在constructor函数中使用
1 | class Phone{ |
ES6 子类对父类方法重写:直接在子类中添加与父类的同名函数即可
ES6 类实例的get和set
get和set方法时默认缺省的,如果声明了
get/set 变量名(){}
函数,则在读取或修改某个变量时时,都会调用一次该变量的get
或set
,在get
中可以操控返回值,在set
中可以操控修改的值1
2
3
4
5
6
7
8
9
10
11
12
13class Phone{
constructor(brand){
this.brand = brand;
}
get brand(){
console.log("读取品牌信息")
}
set brand(val){
console.log("设置品牌信息")
}
}
let iPhone = new Phone()
iPhone.brand = 'Apple';
ES6 对象方法扩展
- 判断两个数值是否相等:
Object.is(a,b)
- 合并对象:
Object.assign(dstObj,srcObj)
,源对象会将属性合并到目标对象中,且覆盖目标对象相同的属性值 - 设置对象的隐式原型:
Object.setPrototypeOf(dstObj,srcObj)
,将源对象设置为目标对象的隐式原型__proto__
- 判断两个数值是否相等:
静态方法
在class声明的类中,使用static
声明的方法将不能被class所创建的实例所调用,只能通过class本身进行调用
1 | class Father{ |
静态属性
ES6之前,在构造函数上添加属性,其属性属于函数对象,并不属于构造函数本身,由此构造函数构造的对象不能得到构造函数对象的属性
1
2
3
4
5
6
7function Phone(){
}
Phone.name = 'xxx';
Phone.call = function()=>{/*...*/};
let iPhone = new Phone();
console.log(iPhone.name) //无法输出在ES6的类中,添加静态方法只需要将前缀改为
ststic
即可1
2
3
4
5
6
7class Phone{
static name = 'xxx';
static call(){
//...
}
}
console.log(Phone.name) //只能通过此形式访问静态属性继承:
扩展运算符…
将伪数组转换成数组
1 | const divs = document.querySelectorAll("div"); |
ES6的模块引入方式
在过去,我们会使用require()
函数来从外部文件或模块中引入函数或者代码。这时候会遇到一个问题:有些文件或者模块会特别大,但你却往往只需要引入其中的一些核心代码。
ES6 给我们提供了import
这个便利的工具。通过它,我们能够从外部的文件或者模块中选择我们需要的部分进行引入,从而节约载入的时间和内存空间。
ES6 同样提供的 export
/ export default
关键字 来暴露一个模块的方法或者变量,具体使用方式请查阅MDN文档
ES6 模块化
在js文件中使用
export
/export default
暴露函数在html文件中使用: 使用模块的函数
1
2
3
4
5
6<script type="module">
import * as module1 from "./xxx.js" //可使用as 进行重命名
import {xxx} from "./xxx.js" //解构赋值
import {default as module3} from "./xxx.js" //将默认暴露的模块在使用时重命名
import module4 from "./xxx.js" //直接使用,只能针对默认暴露的模块
</script>暴露模块的三种方式:
export xxx
export {a,b,c,...}
export default {}
,访问时多一层default
使用入口js文件
- 创建一个
app.js
文件,并在html中引入这个作为模块的js文件 - 在
app.js
文件中引入其他的模块,并使用
- 创建一个
兼容性:使用
babel
将 ES6 转换为 ES5 语法- 需要使用npm安装
babel-cli
babel-present-env
browserify(webpack)
- 运行
npx babel 源js文件/文件夹 目的js文件/文件夹
- 使用入口文件将一些列模块文件进行打包:
npx browserify 源入口js文件 -o 目标入口js文件
- 需要使用npm安装
Symbol 基本数据类型
- ES6引入的第六个基本数据类型,标识独一无二的值,类似于字符串
- symbol 的值是唯一的,用来解决命名冲突问题
- symbol不能与其他数据进行任何运算,只能进行相等判断
- symbol定义的对象属性不能使用
for ... in ...
来遍历,但是可以使用reflect.ownKeys()
来获取key - 使用场景:
- 为对象添加独一无二的值/函数,避免冲突
1 | let a = Symbol("aaa") |
迭代器
Iterator是一种接口,为不同 数据结构提供同一的访问机制,任何数据结构只要部署Iterator
接口皆可以完成遍历操作
每个可迭代的数据类型的实例的隐式原型中都包含一个Symbol(Symbol.iterator)
1 | const FRUIT = ['apple','banana','orange']; |
自定义遍历对象:
自定义一个对象中的迭代器,迭代指定部分的数据:
1 | let obj2 = { |
生成器函数
生成器函数是ES6提供的异步编程解决方案之一,语法行为与普通函数完全不同,生成器函数默认返回一个迭代对象,可使用使用迭代器的next()
方法 来手动执行函数内部每一行代码
1 | function * gen(){ |
迭代器对象的netx()
方法的传参可缺省,默认值为调用的函数
yield
- yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的return关键字。
- yield实际返回一个迭代器对象 例如
{value:" ",done: false }
- yield 只能配合生成器函数使用
- yield并不能直接生产值,而是产生一个等待输出的函数
- 某个函数包含了yield,意味着这个函数已经是一个Generator
- 除IE外,其他所有浏览器均可兼容(包括win10 的Edge)
- 如果yield在其他表达式中,需要用()单独括起来
- next()可无限调用,但既定循环完成之后总是返回
undeinded
1 | function * count() { |
yield参数
1 | function * test(x) { |
当next()
传入参数时,只有上一个yield整体等于传入参数,当前迭代的yield仍然返回之后的表达式的值
异步编程示例1:解决回调地狱
1 | // 回调地狱 |
异步编程示例2:模拟业务处理逻辑
1 | //模拟购物传递信息流程:获取用户数据 → 获取订单数据 → 获取商品数据 |
Promise
ES6 引入的异步编程结局方案,Promise
是一个构造函数,用来封装异步操作,且可获取成功与失败的结果,
Promise
接收一个函数作为参数,其中函数的参数分为resolve
和reject
两个函数来分别处理成功与失败两种状态,函数内部进行一系列异步操作,在最后调用resolve(data)
和 reject(data)
分别赋予Promise实例成功与失败两种状态:
- 调用
resolve(data)
,返回一个状态为成功(fulfilled)的Promise
对象,可以使用then()
进行进一步处理 - 调用
reject(data)
,返回一个失败(rejected)的Promise
对象,如果没有使用then()
进行进一步失败处理,浏览器就会抛出异常,值为data,
在执行以上操作后可以调用:
then()方法
then()方法接收两个函数:
function(value){...}
function(reason){...}
- 当p在初始化时的异步操作调用了
reslove()
之后,执行then()
方法中的第一个回调函数function(value){...}
代码,value
为reslove()
的参数 - 当p在初始化时的异步操作调用了
reject()
之后,执行then()
方法中的第二个回调函数function(reason){...}
代码,reason
为reject()
的参数
- 当p在初始化时的异步操作调用了
promise.then()
方法在调用后,返回一个Promise对象。当在
promise.then()
的成功/失败的回调中使用return
返回一个非Promise对象的值
时,promise.then()
仍然返回一个状态为成功(fulfilled)的Promise对象,其中带有promise状态以及返回的值,其中Object类型会被转换成Object
字符串当在
promise.then()
中使用return
返回了一个Promise对象
时,此时promise.then()
返回的promise
对象的成功与否 由 返回的promise
对象状态 决定当在
promise.then()
中使用throw new Error()
或者thow "error"
抛出错误,则promise.then()
返回的Promise
对象为失败状态,且promise失败值为thow
抛出值
案例1:使用Promise处理文件异步读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const fs = require('fs')
const promise = new Promise((resolve,reject) =>{
fs.readFile('./test.txt',(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
promise.then(value => {
console.log(value.toString());
},reason =>{
console.log(reason)
})案例2:使用Promise 发送AJAX请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const promise = new Promise((resolve,reject) => {
const xhr = new XmlHttpRequest();
xhr.open("get","");
xhr.send();
xhr.onreadstatechange = function(){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300){
console.log(xhr.response);
resolove(xhr.response);
}
else{
console.log(xhr.status)
reject(xhr.status)
}
}
}
})
promise.then(value => {
console.log(value.toString());
},reason => {
console.log(reason)
})state往往就是一个实体固有的状态。
status则偏向于运行时状态。
Promise.then()
方法的链式调用:
在链式调用的then()方法中,在回调函数中的返回值会作为下一个then()方法中回调的
value
或reason
值,只要在then()
的所有回调中使用return
返回了非Promise
对象 则状态均为成功reslove
在
then()
所有回调函数内使用throw
抛出字符串、错误对象 new Error(),则返回的Promise对象状态均为 失败reject
1
2
3
4promise.then(value => {}, reason => {})
.then(value => {}, reason => {})
.then(value => {}, reason => {})
//...Promise.then()
模拟场景:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35const p = new Promise(function(reslove, reject) {
setTimeout(() => {
let userId = 2;
if (userId) {
console.log("请求到了userId");
reslove(userId)
} else {
console.log("没有请求到userId");
reject();
}
}, 1000)
})
const a = p.then(value => {
return new Promise(function(reslove, reject) {
setTimeout(() => {
console.log("请求到了用户信息");
reslove(value + "用户信息")
}, 1000)
})
}, reason => {
throw '没有请求到用户的用户信息'
})
.then(value => {
return new Promise(function(reslove, reject) {
setTimeout(() => {
console.log("请求到了订单信息");
reslove("订单信息")
}, 1000)
})
}, reason => {
throw '没有请求到的订单信息'
})
console.log(p);
console.log(a);catch()方法:catch()的功能与then()第二个回调函数类似,在Promise对象的状态为失败时调用,一般用于获取
Set
集合内部的值不会重复,
添加和删除的方法分别为
add()
delete()
使用
has()
方法检测目标是否存在于集合中,返回t/f使用
clear()
方法清除集合所有元素集合元素个数量使用size()方法得到
应用案例:
数组去重:
1
2let arr = [11,22,33,44,11,22,33]
let result = [...new Set(arr)]求两数组交集
1
2
3
4let arr = [1,2,3,4,5,6];
let arr2 = [3,4,5];
let arrSet = new Set(arr2);
let result = [...new Set(arr)].filter(item => arrSet.has(item))求两数组的并集
1
2
3
4
5
6
7
8
9
10
11let arr = [1, 2, 3, 4, 5, 6];
let arr2 = [3, 4, 5, 6, 7, 8];
let arrSet = new Set(arr);
[...new Set(arr2)].forEach(item => {
if (!arrSet.has(item)) {
arrSet.add(item)
}
})
console.log(arrSet);
//或者
let union = [...new Set([...arr, ...arr2])];
Map
ES6 引入的 新数据结构,类似于对象,结构为键值对集合,但是key的类型不一定是字符串,可以是任何类型的值,map同样实现了迭代器接口,可以使用[...]
,和for of
- Map元素个数量使用
size()
方法得到 - new Map()可以接收一个键值对形式的数组:一般以一个
key
和一个value
组成的基本数组,返回一个Map对象 - 使用
set(key,value)
增加一个元素,并返回更新后的map实例 - 使用
delete(key)
删除一个键值对,并返回布尔值,找到key并删除value返回true,没有找到key返回false - 使用
get(key)
返回key的value值 - 使用
has()
检测map是否包含某个元素 ,返回布尔值 - 使用
clear()
清空map ,返回undefined
ES6 数值方法扩展
使用
Number.EPSILON
来解决js计算精度问题:1
2
3function equal(a,b){
return Math.abs(a-b) < Number.EPSILON ? true:false
}检测数值是否为NaN:
Number.isNaN()
, 返回t/f将小数部分抹去:
Number.trunc(floatNumber)
, 返回值
ES7 新特性
Array.prototype.includes(value)
方法,判断value是否存在于数组中,返回布尔值- 使用
**
进行幂运算:2 ** 2
等同于Math.pow(2,2)
ES8 新特性
async 函数
- async 函数为将await 之后的表达式以及await以下的所有语句都放入异步任务的同步函数
- 使用
async
为前缀声明的函数的返回值为Promise
对象 - 返回的
Primise
对象的状态由async
函数执行的返回值决定,与promise.then()
回调函数的return
原理相同:如果成功,返回成功promise,并将值放入value中;返回失败promise,则将失败值放入value中
await 表达式
await
必须写在async函数内部await
等待右侧promise产生之后,返回值的过程属于异步微任务,await
必须等待到右侧的promise的成功与失败的状态后,线程才会继续执行下面的代码await
右侧表达式一般为Promise
对象await
右侧的Promise
对象如果状态为成功,则返回其成功的值PromiseValue
await
右侧的Promise
对象如果状态为失败,浏览器就会抛出异常,无返回值,需要使用try catch捕获
,catch
的参数e
为Promise
对象失败的值PromiseValue
将 async 函数与 await函数相结合使用,将**await
等待 promise
之后并返回值** 这过程,放入异步微任务,而在await 之后的代码语句,需要等待await
产生结果后,才能继续执行,且是在在微任务中执行,换句话说,遇到await
时就把await
之后的代码放入异步微任务,然后再继续向下执行,
案例1:读取文件,使用async函数接收文件信息
1
2
3
4
5
6
7
8
9
10
11
12function fileReader(){
return new Promise((resolve,reject)=>{
fs.readFile('文件路径',(err,data)=>{
if(err) => reject(err);
resolve(data);
})
})
}
async function dataReceiver(){
let fileData = await fileReader();
return fileData;
}案例2:使用async 与 await 发送AJAX请求并处理消息,这也是
axios
的实现原理之一1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28function AjaxRequest(requestUrl){
return new Promise((resolve,reject)=>{
let xhr = new XmlHttpRequest();
xhr.open('get',requestUrl);
xhr.send();
xhr.onreadystatechange = function (){
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300){
reslove(xhr.response)
}else{
reject(xhr.status)
}
}
}
})
}
async function getMessage(requestUrl){
let requestData;
try{
requestData = await AjaxRequest(requestUrl);
console.log(requestData) //此时await为同步代码,会产生阻塞,当await获得到promise值后才能执行到这一步
return requestData;
}
catch(e) {
console.log(e);
return e;
}
}案例3:在React组件生命周期中使用async函数 和await 处理请求
1
2
3async function componentDidMount(){
let data = await axios.get('url');
}
ES8 对象扩展
Object.keys(object)
获取对象所有的键,返回一个数组Object.values(object)
获取对象所有的键值,返回一个数组Object.values(object)
获取对象的所有键值,以对象的形式生成一个数组,可以通过数组创建map
对象获得对象属性的描述对象
Object.getOwnPropertyDescriptions(object)
,返回一个对象,描述object内部所有属性的选项设置(可读、可写、可枚举等)ES5 补充:
Object.defineProperty()
,对对象属性进行监听,注意不能在get/set中直接调用被修改的属性值,否则造成死循环1
2
3
4
5
6Object.defineProperty(obj,propertyName,{
//为此当前对象属性设置函数,例如set、get
//钩子分别在获取、设置属性值时调用
get(){}
set(){}
})