ES六、ES七、ES8特性一锅炖(ES六、ES七、ES8学习指南)

概述

ES全称ECMAScript,ECMAScript是ECMA制定的标准化脚本语言。目前JavaScript使用的ECMAScript版本为ECMAScript-262javascript

ECMAScript 标准创建在一些原有的技术上,最为著名的是 JavaScript (网景) 和 JScript (微软)。它最初由网景的 Brendan Eich 发明,第一次出现是在网景的 Navigator 2.0 浏览器上。Netscape 2.0 以及微软 Internet Explorer 3.0 后序的全部浏览器上都有它的身影。html

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

了解这些特性,不只能使咱们的编码更加的符合规范,并且能提升咱们Coding的效率。vue

ES6的特性

ES6的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)以后才将其标准化。两个发布版本之间时间跨度很大,因此ES6中的特性比较多。java

在这里列举几个经常使用的:react

  • 模块化
  • 箭头函数
  • 函数参数默认值
  • 模板字符串
  • 解构赋值
  • 延展操做符
  • 对象属性简写
  • Promise
  • Let与Const

1.类(class)

对熟悉Java,object-c,c#等纯面向对象语言的开发者来讲,都会对class有一种特殊的情怀。ES6 引入了class(类),让JavaScript的面向对象编程变得更加简单和易于理解。git

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 函数,不然在新建实例的时候会报错.
    // 若是没有置顶consructor,默认带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中模块做为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每个模块都有本身单独的做用域,模块之间的相互调用关系是经过 export 来规定模块对外暴露的接口,经过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。程序员

导出(export)

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

导出变量编程

//test.js
export var name = 'Rainbow'
复制代码

心得:ES6不只支持变量的导出,也支持常量的导出。 export const sqrt = Math.sqrt;//导出常量c#

ES6将一个文件视为一个模块,上面的模块经过 export 向外输出了一个变量。一个模块也能够同时往外面输出多个变量。

//test.js
 var name = 'Rainbow';
 var age = '24';
 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,能帮你很好的解决this的指向问题。有经验的JavaScript开发者都熟悉诸如var self = this;var that = this这种引用外围this的模式。但借助=>,就不须要这种模式了。

箭头函数的结构

箭头函数的箭头=>以前是一个空括号、单个的参数名、或用括号括起的多个参数名,而箭头以后能够是一个表达式(做为函数的返回值),或者是用花括号括起的函数体(须要自行经过return来返回值,不然返回的是undefined)。

// 箭头函数的例子
()=>1
v=>v+1
(a,b)=>a+b
()=>{
    alert("foo");
}
e=>{
    if (e == 0){
        return 0;
    }
    return 1000/e;
}
复制代码

心得:不管是箭头函数仍是bind,每次被执行都返回的是一个新的函数引用,所以若是你还须要函数的引用去作一些别的事情(譬如卸载监听器),那么你必须本身保存这个引用。

卸载监听器时的陷阱

错误的作法

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);
    }
    componentWillUnmount(){
        AppStateIOS.removeEventListener('change', this.onAppPaused);
    }
    onAppPaused = (event) => {
        //把函数直接做为一个arrow function的属性来定义,初始化的时候就绑定好了this指针
    }
}
复制代码

须要注意的是:不管是bind仍是箭头函数,每次被执行都返回的是一个新的函数引用,所以若是你还须要函数的引用去作一些别的事情(譬如卸载监听器),那么你必须本身保存这个引用。

4.函数参数默认值

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

function foo(height = 50, color = 'red')
{
    // ...
}
复制代码

不使用默认值:

function foo(height, color)
{
    var height = height || 50;
    var color = color || 'red';
    //...
}
复制代码

这样写通常没问题,但当参数的布尔值为false时,就会有问题了。好比,咱们这样调用foo函数:

foo(0, "")
复制代码

由于0的布尔值为false,这样height的取值将是50。同理color的取值为‘red’。

因此说,函数参数默认值不只能是代码变得更加简洁并且能规避一些问题。

5.模板字符串

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

不使用模板字符串:

var name = 'Your name is ' + first + ' ' + last + '.'
复制代码

使用模板字符串:

var name = `Your name is ${first} ${last}.`
复制代码

在ES6中经过${}就能够完成字符串的拼接,只须要将变量放在大括号之中。

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];
复制代码

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

let objClone = { ...obj };
复制代码

应用场景

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

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

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

//使用延展操做符
console.log(sum(...numbers));// 6
复制代码

构造数组

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

const stuendts = ['Jine','Tom']; 
const persons = ['Tony',... stuendts,'Aaron','Anna'];
conslog.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 clonedObj = { ...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.支持let与const

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

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

{
  var a = 10;
}

console.log(a); // 输出10
复制代码

使用let与const定义的变量为块级做用域:

{
  let a = 10;
}

console.log(a); //-1 or Error“ReferenceError: a is not defined”
复制代码

ES7的特性

在ES6以后,ES的发布频率更加频繁,基本每一年一次,因此自ES6以后,每一个新版本的特性的数量就比较少。

  • includes()
  • 指数操做符

1. Array.prototype.includes()

includes() 函数用来判断一个数组是否包含一个指定的值,若是包含则返回 true,不然返回false

includes 函数与 indexOf 函数很类似,下面两个表达式是等价的:

arr.includes(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(calculateExponent(2, 10)); // 输出1024
console.log(Math.pow(2, 10)); // 输出1024
复制代码

使用指数操做符

使用指数运算符**,就像+、-等操做符同样:

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()// Success
this.doLogin2()// Success
复制代码

async/await的几种应用场景

接下来咱们来看一下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('d1 is null');
    }
    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 vals=Object.keys(obj).map(key=>obj[key]);
console.log(vals);//[1, 2, 3]
复制代码

使用Object.values() :ES8

const values=Object.values(obj1);
console.log(values);//[1, 2, 3]
复制代码

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

3.Object.entries

Object.entries()函数返回一个给定对象自身可枚举属性的键值对的数组。

接下来咱们来遍历上文中的obj对象的全部属性的key和value:

不使用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(obj1)){
	console.log(`key: ${key} value:${value}`)
}
//key:a value:1
//key:b value:2
//key:c value:3
复制代码

4.String padding

在ES8中String新增了两个实例函数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.00    
复制代码

String.padEnd(targetLength,padString])

  • targetLength:当前字符串须要填充到的目标长度。若是这个数值小于当前字符串的长度,则返回当前字符串自己。
  • padString:(可选) 填充字符串。若是字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其余部分会被截断,此参数的缺省值为 " ";
console.log('0.0'.padEnd(4,'0')) //0.00    
console.log('0.0'.padEnd(10,'0'))//0.00000000
复制代码

4.函数参数列表结尾容许逗号

这是一个不痛不痒的更新,主要做用是方便使用git进行多人协做开发时修改同一个函数减小没必要要的行变动。

不使用ES8

//程序员A
var f = function(a,
  b
   ) { 
  ...
  }

//程序员B
var f = function(a,
  b,   //变动行
  c   //变动行
   ) { 
  ...
  }

//程序员C
var f = function(a,
  b,
  c,   //变动行
  d   //变动行
   ) { 
  ...
  }
复制代码

使用ES8

//程序员A
var f = function(a,
  b,
   ) { 
  ...
  }

//程序员B
var f = function(a,
  b,
  c,   //变动行
   ) { 
  ...
  }

//程序员C
var f = function(a,
  b,
  c,
  d,   //变动行
   ) { 
  ...
  }
复制代码

5.Object.getOwnPropertyDescriptors()

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

函数原型:

Object.getOwnPropertyDescriptors(obj)
复制代码

返回obj对象的全部自身属性的描述符,若是没有任何自身属性,则返回空对象。

const obj2 = {
	name: 'Jine',
	get age() { return '18' }
};
Object.getOwnPropertyDescriptors(obj2)
// {
//   age: {
//     configurable: true,
//     enumerable: true,
//     get: function age(){}, //the getter function
//     set: undefined
//   },
//   name: {
//     configurable: true,
//     enumerable: true,
//		value:"Jine",
//		writable:true
//   }
// }
复制代码

总结

技术更替的车轮一直在前进中,JavaScript的规范和标准也在不断的制定和完善。你会发现ECMAScript 新版的不少特性已是Typescript,浏览器或其余polyfills的一部分,就拿ES8的async/await来讲,它是2017年6月被归入ECMAScript的,但我在2016年的时候就已经开始使用这个特性了,这些特性一般由ECMAScript议员提交,而后会出如今在将来的某个ECMAScript版本中。

参考

相关文章
相关标签/搜索