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版本中。

參考

相關文章
相關標籤/搜索