快速学习ES6、ES7、ES8基础指南

概述

ECMAScript版本 发布时间 新增特性
ES5 2009年11月 扩展了ObjectArrayFunction的功能
ES6 2015年5月 类,模块化,箭头函数,函数参数默认值等
ES7 2016年3月 includes,指数操作符
ES8 2017年6月 sync/awaitObject.values()Object.entries()Stringpadding

ES6的特性

安利几个常用的:
– 类
– 模块化
– 箭头函数
– 函数参数默认值
– 解构赋值
– 延展操作符
– 对象属性简写
Promise
let const

1.类 class

JavaScript的面向对象编程变得更加简单和易于理解

class Animal {
    // 构造函数,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数
    constructor(name,color) {
        this.name = name;
        this.color = color;
    }
    // toString 原型对象上的属性
    toString() {
        console.log('name:' + this.name + ',color:' + this.color);
    }
}

var animal = new Animal('dog','white'); // 实例化Animal
animal.toString();

console.log(animal.hasOwnProperty('name')); // true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true

class Cat extends Animal {
    constructor(action) {
        // 子类必须要在 constructor 中指定 super 函数,否则在新建实例的时候会报错。
        // 如果没有置顶 constructor。默认带 super 函数的 constructor 将会被添加。
        super('cat','white');
        this.action = action;
    }
    toString() {
        console.log(super.toString());
    }
}

var cat = new Cat('catch');
cat.toString();

// 实例 cat 是 Cat 和 Animal 的实例,和ES5完全一致
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true

2.模块化Module

ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要有exportimport组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过export来规定模块对外暴露的接口,通过import来引用它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。

导出exprot

ES6允许在一个模块中使用export来导出多个变量或者函数。
导出变量

// test.js
export var name = 'Rainbow'

ES6不仅支持变量的导出,也支持常量导出。export const sqrt = Math.sqrt // 导出常量

ES6将一个文件视为一个模块,上面的模块通过exprot向外输出了一个变量。一个模块也可以同时往外面输出多个变量。

// test.js
var name = 'Rainbow';
var age = '22';
export {name, age};

导出函数

// myModule.js
export function myModule(someArg) {
    return someArg;
}

导入import

定义好模块的输出以后就可以在另外一个模块通过import引用。

import {myModule} from 'myModule'; // main.js
import {name, age} from 'test'; //test.js

关于import语句可以同时导入默认函数和其它变量。import defaultMethod, {otherMethod} from 'xxx.js';

3.箭头Arrow函数

这是ES6特性之一。=>不只是关键字function的简写,箭头函数与包围它的代码共享同一个this

箭头函数的结构

// 箭头函数的安利
()=>1
v=>v+1
()=>{
    alert("foo");
}
e=>{
    if(e == 0){
        return 0;
    }
    return 1000/e;
}

卸载监听器时的注意

错误的做法

class PauseMenu extends React.Component{
    componentWillMount(){
        AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));
    }
    componentWillUnmount(){
        AppstateIOS.removeEventListener('change', this.onAppPaused.bind(this));
    }
    onAppPaused(event){

    }
}

正确的做法

class PauseMenu extends React.Component{
    constructor(props){
        super(props);
        this._onAppPaused = this.onAppPaused.bind(this);
    }
    componentWillMount(){
        AppStateIOS.addEventListener('change', this._onAppPaused);
    }
    componentWillUnmount(){
        AppstateIOS.removeEventListener('change', this._onAppPaused);
    }
    onAppPaused(event){

    }
}

除上述的做法外,还可以这样做:

class PauseMenu extends React.Component{
    componentWillMount(){
        AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));
    }
    componentWillUnmount(){
        AppstateIOS.removeEventListener('change', this.onAppPaused.bind(this));
    }
    onAppPaused = (event) => {
        // 把函数直接作为一个 arraw function 的属性来定义,初始化的时候就绑定好了this指针。
    }
}

注意:不论是bind还是箭头函数,每次被执行都返回的是一个新的函数引用,因此如果还需要函数的引用做其它事情,那么就要保存这个引用。

4.函数参数默认值

ES6支持在定义函数的时候为其设置默认值:

function foo(height = 50, color = 'pink')
{
    // ...
}

不是用默认值

function foo(height, color)
{
    var height = height || 50;
    var color = color || 'pink';
    // ...
}

这样一般没问题,但是参数的布尔值为false时,就会有问题了!例如:

foo(0,"");

因为0的布尔值为false,这样height的取值就是50了,同理color取值就是pink了。

5.模板字符串

ES6支持模板字符串,使得字符串更加简洁直观。

不使用模板字符串

var name = 'Your name is ' + first + ' ' + last + '.';

使用模板字符串

var name = 'Your name is ${first} ${last}.';

6.解构赋值

解构赋值语法是JavaScript的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。

获取数组中的值

从数组中获取值并赋值到变量中,变量的顺序与数组中对象顺序对应。

var foo = ["one", "two", "three" ,"four"];

var [one,two,three] = foo;

console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"

// 如果要忽略某些值,可以按照下面的写法获取值
var [first, , , last] = foo;
console.log(first); // "one"
console.log(last); // "four"

// 也可以这样编写
var a, b; //先申明变量
[a, b] = [1,2];
console.log(a); // 1
console.log(b); // 2

如果没有从数组中获取到值,可以为变量设置一个默认值。

var a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

通过解构赋值可以方便的交换两个变量的值。

var a=1;
var b=3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

获取对象中的值

const student = {
    name:'Ming',
    age:'18',
    city:'Shanghai'
};

const {name,age,city} = student;
console.log(name); // "Ming"
console.log(age); // "18"
console.log(city); // "Shanghai"

7.延展操作符(Spread operator)

延展操作符...可以在函数调用数组构造时,将数组表达式或者string在语法层面展开;还可以在构造对象时,将对象表达式按key-value的方式展开。

语法

函数调用:

myFunction(...iterableObj);

数组构造或字符串:

[...iterableObj, '4', ...'hello', 6];

构造对象时,进行克隆或者属性拷贝(ECMAScript2018规范新特性):

let objClone = { ...obj };

应用场景

在函数调用时使用延展操作符

function sum(x, y, z){
    return x + y + z;
}
const numbers = [1, 2, 3];

// 不使用延展操作符
console.log(sum.apply(null, numbers)); // 6

// 使用延展操作符
console.log(sum(...numbers)); // 6

构造数组

没有展开语法的时候,只能组合使用 pushspliceconcat等方法,来将已有数组元素变成新数组的一部分。有了展开语法,构造新数组会变得更简单、更优雅:

const stuendts = ['Jine','Tom'];
const persons = ['Tony', ... stuendts,'Aaron','Anna'];
console.log(persions)// ["Tony", "Jine", "Tom", "Aaron" ,"Anna"];

和参数列表的展开类似,...在构造数组时,可以在任意位置多次使用。

数组拷贝

var arr = [1, 2, 3];
var arr2 = [...arr]; // 等同于 arr.slice()
arr2.push(4);
console.log(arr2); // [1, 2, 3, 4]

展开语法和Object.assign()行为一致,执行的都是浅拷贝(只遍历一层)。

链接多个数组

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
var arr3 = [...arr1, ...arr2]; // 将 arr2 中所有元素附加到 arr1 后面并返回
// 等同于
var arr4 = arr1.concat(arr2);

在ECMAScript 2018中延展操作符增加了对对象的支持:

var obj1 = { foo: 'bar', x:42 };
var obj2 = { foo: 'baz', y:13 };

var cloneObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x:42 }

var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象:{ foo: "baz", x: 42 y: 13 }

React中的应用

通常我们在封装一个组件时,会对外公开一些props用于实现功能。大部分情况下在外部使用都应显示的传递props。但是当传递大量的props时,会非常繁琐,这时我们可以使用 ...(延展操作符,用于取出参数对象的所有可遍历属性)来进行传递。

一般情况下应该的安利:

<CustomComponent name ='Jine' age = {21} />

使用...,等同于上面的写法

const params = {
    name: "Jine",
    age: 21
}
<CustomComponent {...params} />

配合结构赋值避免传入一些不需要的参数

var params = {
    name: '123',
    title: '456',
    type: 'aaa',
}
var { type, ...other } = params;

<CustomComponent type='normal' number={2} {...other} />
// 等同于
<CustomComponent type='normal' number={2} name='123' title='456' />

8.对象属性简写

在ES6中允许我们在设置一个对象的属性的时候不指定属性名。

不使用ES6

const name = 'Ming' , age = '18' , city = 'Shanghai';

const student = {
    name:name,
    age:age,
    city:city
}
console.log(student); // {name: "Ming", age: "18", city: "Shanghai"}

对象中必须包含属性和值,显得非常沉余。

使用ES6

const name = "Ming",age="18",city="Shanghai";

const student = {
    name,
    age,
    city
};
console.log(student); // {name: "Ming", age: "18", city: "Shanghai"}

对象中直接写变量,非常简洁。

9.Promise

Promise 是异步编程的一种解决方案,比传统的解决方案callback更加优雅。它最早是由社区提出和实现的,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

不使用ES6

嵌套两个setTimeout回调函数:

setTimeout(function(){
    console.log('hello'); // 1秒后输出"hello"
    setTimeout(function(){
        console.log('hi'); // 2秒后输出"hi"
    }, 1000);
}, 1000);

使用ES6

var waitSecond = new Promise(function(resolve, reject){
    setTimeout(resolve, 1000);
});

waitSecond.then(function(){
    console.log("hello"); // 1秒后输出"hello"
    return waitSecond;
}).then(function(){
    console.log('hi'); // 2秒后输出"hi"
});

上面的代码使用两个then来进行异步编程串行化,避免了回调地狱。

10.支持letconst

在之前JS是没有块级作用域的,const与let填补了这方面的空白,const与let都是块级作用域。

使用var定义的变量为函数级作用域:

{
    var a = 10;
}
console.log(a); // 输出10

使用letconst定义的变量为块级作用域:

{
    let a = 10;
}
console.log(a); // Uncaught ReferenceError: a is not defined

ES7的新特性

在ES6之后,ES的发布频率更加频繁,基本每年一次,所以自ES6之后,每个版本的特性的数量就比较少。
includes()
– 指数操作符

1.Array.prototype.includes()

includes()函数用来判断一个数组是否包含一个指定的值,如果包含则返回true,否则犯规false
includes()函数与indexOf函数很相似,下面两个表达式是等价的:

arrincludes(x);
arr.indexOf(x) >= 0;

接下来看判断数字中是否包含某个元素:

在ES7之前的做法

使用indexOf()验证数组中是否存在某个元素,这时需要根据返回值是否为-1来判断:

let arr = ['react', 'angular', 'vue'];

if (arr.indexOf('react') !== -1 ) {
    console.log('react 存在');
}

使用ES7的includes()

使用includes()验证数组中是否存在某个元素,这样更加直观简单:

let arr = ['react', 'angular', 'vue'];

if (arr.includes('react')) {
    console.log('react 存在');
}

2.指数操作符

在ES7中引入了指数运算符****具有与Math.pow(..)等效的计算结果。

不使用指数操作符

使用自定义的递归函数calculateExponent或者Math.pow()进行指数运算:

function calculateExponent(base, exponent){
    if (exponent === 1) {
        return base;
    } else {
        return base * calculateExponent(base, exponent-1);
    }
}

使用指数操作符

使用指数运算符**,就像+-等操作符一样:

console.log(2**10); // 输出1024

ES8的特性

  • async/await
  • Object.values()
  • Object.entries()
  • String padding
  • 函数参数列表结尾允许逗号
  • Object.getOwnPropertyDescriptors()

1.async/await

在ES8中加入了对async/await的支持,也就是所说的异步函数,这是一个很实用的功能。async/await将我们从头痛的回调地狱中解脱出来了,使整个代码看起来很简洁。

使用async/await与不使用async/await的差别:

login(userName){
    return new Promise((resolve, reject)=>{
        setTimeout(()=>{
            resolve('1001');
        }, 600);
    });
}

getData(userID){
    return new Promise((resolve, reject) => {
        setTimeout(()=>{
            if (userID === '1001') {
                resolve('SUCCESS');
            } else {
                reject('FAIL');
            }
        },600);
    });
}

// 不使用async/await ES7
doLogin(userName){
    this.login(userName).then(this.getData).then(result => {
        console.log(result);
    });
}

// 使用async/await ES8
async doLogin2(userName) {
    const userID = await this.login(userName);
    const result = await this.getData(userID);
}

this.doLogin(1);// SUCCESS
this.doLogin2(1);// SUCCESS

async/await的几种应用场景

获取异步函数返回值

异步函数本身返回一个Promise,所以可以通过then来获取异步函数的返回值。

async function charCountAdd(data1,data2){
    const d1 = await charCount(data1);
    const d2 = await charCount(data2);
    return d1+d2;
}
charCountAdd('Hello', 'Hi').then(console.log); //通过then获取异步函数的返回值
function charCount(data){
    return new Promise((resolve, reject) => {
        setTimeout(()=>{
            resolve(data.length);
        }, 1000);
    });
}

async/await在并发场景中的应用

对于上诉的例子,我们调用await两次,每次都是等待1秒一共是2秒,效率比较低,而且两次await的调用并没有依赖关系,那能不能让其并发执行呢?答案是肯定的,接下来通过Promise.all来实现await的并发调用。

async function charCountAdd(data1, data2){
    const [d1,d2] = await Promise.all([charCount(data1),charCount(data2)]);
    return d1+d2;
}
charCountAdd('Hello','Hi').then(console.log);
function charCount(data) {
    return new Promise( (resolve, reject) => {
        setTimeout( () => {
            resolve(data.length);
        }, 1000);
    });
}

通过上述代码实现了两次charCount的并发调用,Promise.all接受的是一个数组,它可以将数组中的Promise对象并发执行。

async/await的几种错误处理

第一种:捕捉整个async/await函数的错误

async function charCountAdd(data1, data2){
    const d1 = await charCount(data1);
    const d2 = await charCount(data2);
    return d1+d2;
}
charCountAdd('Hello','Hi').then(console.log).catch(console.log);//捕捉整个`async/await`函数的错误

这种方式可以捕捉整个charCountAdd运行过程中出现的错误,错误可能是由charCountAdd本身产生的,也可能是由对data1的计算中或data2的计算中产生的。

第二种:捕捉单个的await表达式的错误

async function charCountAdd(data1, data2) {
    const d1 = await charCount(data1).catch(e => console.log('d1 is null'));
    const d2 = await charCount(data2).catch(e => console.log('d2 is null'));
    return d1+d2;
}
charCountAdd('Hello','Hi').then(console.log);

通过这种方式可以捕捉每一个await表达式错误。如果既要捕捉每个await表达式错误,又要捕捉整个charCountAdd函数的错误,可以在调用charCountAdd的时候加个catch进行捕捉。

charCountAdd('Hello','Hi').then(console.log).catch(console.log);// 捕捉整个async/await函数错误

第三种:同时捕捉多个await表达式的错误

async function charCountAdd(data1, data2) {
    let d1,d2;
    try {
        d1 = await charCount(data1);
        d2 = await charCount(data2);
    } catch (e) {
        console.log(e);
    }
    return d1+d2;
}
charCountAdd('Hello','Hi').then(console.log);
function charCount(data) {
    return new Promise( (resolve, reject) => {
        setTimeout( () => {
            resolve(data.length);
        }, 1000); 
    });
}

2.Object.values()

Object.values()是一个与Object.keys()类似的新函数,但返回的是Object自身属性的所有值,不包括继承的值。
假设我们要遍历如下对象obj的所有值:

const obj = {a:1, b:2, c:3};

不使用Object.values():ES7

const val = Object.keys(obj).map(key => obj[key]);
console.log(val);// [1,2,3]

使用Object.values():ES8

const values = Object.values(obj);
console.log(values); // [1,2,3]

从上述代码中可以看出Object.values()为我们省去了遍历key,并根据这些key获取value的步骤。

3.Object.entries

Object.entries()函数返回一个给定对象自身可枚举属性的键值对的数组。
接下来我们来遍历上文中的obj对象的所有属性的keyvalue:

不使用Object.entries():ES7

Object.keys(obj).forEach(key => {
    console.log('key:' + key + ' value:' + obj[key]);
});
// key:a value:1
// key:b value:2
// key:c value:3

使用Object.entries():ES8

for(let [key,value] of Object.entries(obj)) {
    console.log(`key: ${key} value:${value}`);
}
// key:a value:1
// key:b value:2
// key:c value:3

4.String padding

ES8String新增了两个实例函数String.prototype.padStartString.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。

String.padStart(targetLength,[padString])

  • targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
  • padString:(可选)填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为“ ”。
console.log('0.0'.padStart(4,'10')); //10.0
console.log('0.0'.padStart(20)); //                  0.0

String.padEnd(targetLength,[padString])

  • targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
  • padString:(可选)填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为“ ”。
console.log('0.0'.padEnd(4,'0')); //0.00
console.log('0.0'.padEnd(10,'0')); //0.00000000

5.函数参数列表结尾允许逗号

一个小更新而已,不用多说了。只是方便应用而已。

var f = function(a, b, c,){
    // tudo
}

6.Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors()函数用来获取一个对象的所有自身属性的描述符,如果没有任何属性,则返回空对象。

函数原型:

Object.getOwnPropertyDescriptors(obj)

返回obj对象的所有自身属性的描述符,如果没有任何属性,则返回空对象。

const obj = {
    name: 'eson',
    get age() {
        return '18';
    }
};
Object.getOwnPropertyDescriptors(obj);

总结

技术更替的车轮一直在前进中,JavaScript的规范和标准也在不断的制定和完善。发现ECMAScript新版的很多特效已经是Typescript,浏览器或其他polyfills的一部分,例如ES8async/await来说,它是2017年6月被纳入ECMAScript的,但是2016年的时候就已经开始使用这个特性了,这些特性通常由ECMAScript议员提交,然后会出现在未来的某个ECMAScript版本中。


快速学习ES6、ES7、ES8基础指南插图
00:00/00:00

eson

🌀偶遇故人点头微笑^_^心像透明水滴☝没有喜悲…🍻

2 条评论

小鸡鸡 · 2020年10月6日 下午8:59

总结
技术更替的车轮一直在前进中,JavaScript的规范和标准也在不断的制定和完善。发现ECMAScript新版的很多特效已经是Typescript,浏览器或其他polyfills的一部分,例如ES8的async/await来说,它是2017年6月被纳入ECMAScript的,但是2016年的时候就已经开始使用这个特性了,这些特性通常由ECMAScript议员提交,然后会出现在未来的某个ECMAScript版本中。
博主总结得很到位,学到了很多东西,加油!!!!!!!

小鸡鸡 · 2020年10月6日 下午7:44

666666666

发表评论

电子邮件地址不会被公开。 必填项已用*标注