Javascript之ES7詳解

ES7+ES8

前言

本篇文章主要介紹ES7+ES8的一些新功能,並結合ES6的一些API作出了相應的比較。javascript

ES7

1.Array.prototype.includes()

includes()做用,是查找一個值在不在數組裏,如果存在則返回true,不存在返回false.html

1.基本用法:java

['a', 'b', 'c'].includes('a')     // true
['a', 'b', 'c'].includes('d')     // false
複製代碼

2.接收倆個參數:要搜索的值和搜索的開始索引node

['a', 'b', 'c', 'd'].includes('b')         // true
['a', 'b', 'c', 'd'].includes('b', 1)      // true
['a', 'b', 'c', 'd'].includes('b', 2)      // false
複製代碼

3.與ES6中的indexOf()比較es6

有些時候是等效的npm

['a', 'b', 'c'].includes('a')          //true
['a', 'b', 'c'].indexOf('a') > -1      //true

var arr = [1, 2, 3]
var a = 1;
arr.includes(a)   //true
arr.indexOf(a)    //0 
複製代碼
  • 在判斷 +0 與 -0 時,被認爲是相同的。
[1, +0, 3, 4].includes(-0)    //true
[1, +0, 3, 4].indexOf(-0)     //1
複製代碼
  • 只能判斷簡單類型的數據,對於複雜類型的數據,好比對象類型的數組,二維數組,這些,是沒法判斷的.
var arr = [1, [2, 3], 4]
arr.includes([2, 3])   //false
arr.indexOf([2, 3])    //-1
複製代碼

優缺點比較json

  • 簡便性

includes()返回的是布爾值,能直接判斷數組中存不存在這個值,而indexOf()返回的是索引,這一點上前者更加方便。api

  • 精確性數組

    二者都是採用===的操做符來做比較的,不一樣之處在於:對於NaN的處理結果不一樣。promise

    咱們知道js中 NaN === NaN 的結果是false,indexOf()也是這樣處理的,可是includes()不是這樣的。

    let demo = [1, NaN, 2, 3]
    
    demo.indexOf(NaN)        //-1
    demo.includes(NaN)       //true
    複製代碼

總結:

因爲它對NaN的處理方式與indexOf不一樣,假如你只想知道某個值是否在數組中而並不關心它的索引位置,建議使用includes()。若是你想獲取一個值在數組中的位置,那麼你只能使用indexOf方法。

2.求冪運算符

基本用法:

3 ** 2  //9
效果同
Math.pow(3, 2) //9
複製代碼

因爲是運算符,因此能夠和 +=同樣的用法

var b = 3;
b **= 2;
console.log(b); //9
複製代碼

ES8

1.async await

異步函數async function()

1.1做用

避免有更多的請求操做,出現多重嵌套,也就是俗稱的「回調地獄」

this.$http.jsonp('/login', (res) => {
  this.$http.jsonp('/getInfo', (info) => {
    // do something
  })
})
複製代碼

所以提出了ES6的Promise,將回調函數的嵌套,改成了鏈式調用:

var promise = new Promise((resolve, reject) => {
  this.login(resolve);
})
.then(() => {
  this.getInfo()
})
.catch(() => {
  console.log('Error')
})
複製代碼

1.2聲明方式

異步函數存在如下四種使用形式:

  • 函數聲明: async function foo() {}
  • 函數表達式: const foo = async function() {}
  • 對象的方式: let obj = { async foo() {} }
  • 箭頭函數: const foo = async () => {}

1.3支持返回Promise和同步的值

async用於定義一個異步函數,該函數返回一個Promise。 若是async函數返回的是一個同步的值,這個值將被包裝成一個理解resolve的Promise,等同於return Promise.resolve(value)。 await用於一個異步操做以前,表示要「等待」這個異步操做的返回值。await也能夠用於一個同步的值。

//async await
    //返回Promise
    let timer = async function timer() {
        return new Promise((reslove, reject) => {
            setTimeout(() => {
                reslove('a');
            }, 1000);
        })
    }
    timer().then(result => {
        console.log(result);
    }).catch(err => {
        console.log(err.message);
    })

    //返回同步的值
    let sayHello = async function sayHello() {
        let hi = 'hello world'//等同於return Promise.resolve(hi);
        return hi
    }
    sayHello().then(res => {
        console.log(res)
    }).catch(err => {
        console.log(err.message);
    })
複製代碼

1.4對異常的處理

首先來看下Promise中對異常的處理

1.使用reject

let promise = new Promise((reslove, reject) => {
  setTimeout(() => {
  	reject('promise使用reject拋出異常')  
  }, 1000)
})
promise().then(res => {
  console.log(res)
})
.catch(err => {
  console.log(err)     //'promise使用reject拋出異常'
})

複製代碼

2.使用new Error()

let promise = new Promise((reslove, reject) => {
  	throw new Error('promise使用Error拋出異常') //使用throw異常不支持放在定時器中
})
promise().then(res => {
  console.log(res)
})
.catch(err => {
  console.log(err.message)     //'promise使用Error拋出異常'
})

複製代碼

3.reject一個new Error()

let promise = new Promise((resolve, reject) => {
	
        setTimeout(() => {
            reject(new Error('promise拋出異常'));
        }, 1000);
    })

    promise.then(res => {
        console.log(res);
    })
    .catch(err => {
        console.log(err.message);  //'promise拋出異常'
    })
複製代碼

async對異常的處理也能夠直接用.catch()捕捉到

//async拋出異常
    let sayHi = async sayHi => {
            throw new Error('async拋出異常');
    }
    sayHi().then(res => {
        console.log(res);
    })
    .catch(err => {
        console.log(err.message);
    })
複製代碼

和Promise鏈的對比:

咱們的async函數中能夠包含多個異步操做,其異常和Promise鏈有相同之處,若是有一個Promise被reject()那麼後面的將不會再進行。

let count = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('promise故意拋出異常')
            }, 1000);
        })
    }
    let list = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve([1, 2, 3])
            }, 1000);
        })
    }

    let getList = async () => {
        let c = await count()
        console.log('async')    //此段代碼並無執行
        let l = await list()
        return { count: c, list: l }
    }
    console.time('start');
    getList().then(res => {
        console.log(res)
    })
    .catch(err => {
        console.timeEnd('start')
        console.log(err)
    })
    
    //start: 1000.81494140625ms
    //promise故意拋出異常
複製代碼

能夠看到上面的案例,async捕獲到了一個錯誤以後就會立馬進入.catch()中,不執行以後的代碼

1.5並行

上面的案例中,async採用的是串行處理

count()和list()是有前後順序的

let c = await count()
let l = await list()
複製代碼

實際用法中,如果請求的兩個異步操做沒有關聯和前後順序性能夠採用下面的作法

let res = await Promise.all([count(), list()])
return res

//res的結果爲
//[ 100, [ 1, 2, 3 ] ]
複製代碼

案例詳情爲:

let count = ()=>{
    return new Promise((resolve,reject) => {
        setTimeout(()=>{
            resolve(100);
        },500);
    });
}

let list = ()=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve([1,2,3]);
        },500);
    });
}

let getList = async ()=>{
    let result = await Promise.all([count(),list()]);
    return result;
}
console.time('begin');
getList().then(result => {
    console.timeEnd('begin');  //begin: 505.557ms
    console.log(result);       //[ 100, [ 1, 2, 3 ] ]
}).catch(err => {
    console.timeEnd('begin');
    console.log(err);
});
複製代碼

咱們將count()和list()使用Promise.all()「同時」執行,這裏count()和list()能夠看做是「並行」執行的,所耗時間將是兩個異步操做中耗時最長的耗時。 最後獲得的結果是兩個操做的結果組成的數組。咱們只須要按照順序取出數組中的值便可。

1.6與Generator的關係

先來回顧一下ES6中Generator函數的用法:

function* getList() {
        const c = yield count()
        const l = yield list()
        return 'end'
    }
    var gl = getList()
    console.log(gl.next()) // {value: Promise, done: false}
    console.log(gl.next()) // {value: Promise, done: false}
    console.log(gl.next()) // {value: 'end', done: true}
複製代碼

雖然Generator將異步操做表示得很簡潔,可是流程管理卻不方便(即什麼時候執行第一階段、什麼時候執行第二階段)。此時,咱們便但願能出現一種能自動執行Generator函數的方法。咱們的主角來了:async/await。

ES8引入了async函數,使得異步操做變得更加方便。簡單說來,它就是Generator函數的語法糖。

let getList = async () => {
  const c = await count()
  const l = await list()
}
複製代碼

2.Object.entries()

2.1做用

做用:將一個對象中可枚舉屬性的鍵名和鍵值按照二維數組的方式返回。

若對象是數組,則會將數組的下標做爲鍵值返回。

Object.entries({ one: 1, two: 2 })    //[['one', 1], ['two', 2]]
Object.entries([1, 2])                //[['0', 1], ['1', 2]]
複製代碼

2.2要點

1.如果鍵名是Symbol,編譯時會被自動忽略

Object.entries({[Symbol()]:1, two: 2})  //[['two', 2]]
複製代碼

2.entries()返回的數組順序和for循環同樣,即若是對象的key值是數字,則返回值會對key值進行排序,返回的是排序後的結果

Object.entries({ 3: 'a', 4: 'b', 1: 'c' })    //[['1', 'c'], ['3', 'a'], ['4', 'b']]
複製代碼

3.利用Object.entries()建立一個真正的Map

var obj = { foo: 'bar', baz: 42 };
    
    var map1 = new Map([['foo', 'bar'], ['baz', 42]]); //本來的建立方式
    var map2 = new Map(Object.entries(obj));    //等同於map1

    console.log(map1);// Map { foo: "bar", baz: 42 }
    console.log(map2);// Map { foo: "bar", baz: 42 }
複製代碼

2.3自定義Object.entries()

Object.entries的原理其實就是將對象中的鍵名和值分別取出來而後推動同一個數組中

//自定義entries()
    var obj = { foo: 'bar', baz: 42 };
    function myEntries(obj) {
        var arr = []
        for (var key of Object.keys(obj)) {
            arr.push([key, obj[key]])
        }
        return arr
    }
    console.log(myEntries(obj))
    
    //Generator版本
    function* genEntryies(obj) {
        for (let key of Object.keys(obj)) {
            yield [key, obj[key]]
        }
    }
    var entryArr = genEntryies(obj);
    console.log(entryArr.next().value) //["foo", "bar"]
    console.log(entryArr.next().value) //["baz", 42]
複製代碼

3.Object.values()

3.1做用

做用:只返回本身的鍵值對中屬性的值。它返回的數組順序,也跟Object.entries()保持一致

Object.values({ one: 1, two: 2 })            //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' })    //['c', 'a', 'b']
複製代碼

3.2與Object.keys()比較

ES6中的Object.keys()返回的是鍵名

var obj = { foo: 'bar', baz: 42 };
    console.log(Object.keys(obj)) //["foo", "baz"]
    console.log(Object.values(obj)) //["bar", 42]
    
    //Object.keys()的做用就相似於for...in
    function myKeys() {
        let keyArr = []
        for (let key in obj1) {
            keyArr.push(key)
            console.log(key)
        }
        return keyArr
    }
    console.log(myKeys(obj1)) //["foo", "baz"]
複製代碼

3.3entries()、values()總結

var obj = { foo: 'bar', baz: 42 };
    console.log(Object.keys(obj)) //["foo", "baz"]
    console.log(Object.values(obj)) //["bar", 42]
    console.log(Object.entries(obj)) //[["foo", "bar"], ["baz", 42]]
複製代碼

4.字符串填充

4.1padStart()和padEnd()

字符串填充padStart()padEnd()

用法

String.padStart(targetLength, padding)

參數:字符串目標長度和填充字段

'Vue'.padStart(10)           //' Vue'
'React'.padStart(10)         //' React'
'JavaScript'.padStart(10)    //'JavaScript'
複製代碼

4.2要點

1.填充函數只有在字符長度小於目標長度時纔有效,並且目標長度若是小於字符串自己長度時,字符串也不會作截斷處理,只會原樣輸出

'Vue'.padEnd(10, '_*')           //'Vue_*_*_*_'
'React'.padEnd(10, 'Hello')      //'ReactHello'
'JavaScript'.padEnd(10, 'Hi')    //'JavaScript'
'JavaScript'.padEnd(8, 'Hi')     //'JavaScript'
複製代碼

5.Object.getOwnPropertyDescriptors()

5.1做用

該方法會返回目標對象中全部屬性的屬性描述符,該屬性必須是對象本身定義的,不能是從原型鏈繼承來的。

var obj = {
        id:  1,
        name: '霖呆呆',
        get gender() {
            console.log('gender')
        },
        set grad(d) {
            console.log(d)
        }
    }
    console.log(Object.getOwnPropertyDescriptors(obj))
 //輸出   
{
  gender: {
    configurable: true,
    enumerable: true,
    get: f gender(),
    set: undefined
  },
  grade: {
    configurable: true,
    enumerable: true,
    get: undefined,
    set: f grade(g)
  },
  id: {
    configurable: true,
    enumerable: true,
    value: 1,
    writable: true
  },
  name: {
    configurable: true,
    enumerable: true,
    value: '霖呆呆',
    writable: true
  }
}
複製代碼

第二個參數,用於指定屬性的屬性描述符

Object.getOwnPropertyDescriptors(obj, 'id')

//輸出結果應該爲
{
  id: {
    configurable: true,
    enumerable: true,
    value: 1,
    writable: true
  }
}
複製代碼

可是我在谷歌/火狐瀏覽器試了好像沒有效果,有知道緣由的小夥請留言

5.2與getOwnPropertyDescriptor()比較

ES6中也有一個返回目標對象可枚舉屬性的方法

var obj = {
    id: 1,
    name: '霖呆呆',
    get gender() {
        console.log('gender')
    },
    set grad(d) {
        console.log(d)
    }
}
console.log(Object.getOwnPropertyDescriptor(obj, 'id'))
        
//輸出結果
 {
  id: {
    configurable: true,
    enumerable: true,
    value: 1,
    writable: true
  }
}
複製代碼

二者的區別:一個是隻返回知道屬性名的描述對象,一個返回目標對象全部自身屬性的描述對象

5.3自定義該方法

function myDescriptors(obj) {
            let descriptors = {}
            for (let key in obj) {
                descriptors[key] = Object.getOwnPropertyDescriptor(obj, key)
            }
            return descriptors
        }
        console.log(myDescriptors(obj))
        //返回的結果和該方法同樣
        
        //其中上面自定義方法的for...in也能夠換成,效果也是同樣的
        for (let key of Object.keys(obj)) {
            descriptors[key] = Object.getOwnPropertyDescriptor(obj, key)
        }
複製代碼

6.函數參數支持尾部逗號

該特性容許咱們在定義或者調用函數時添加尾部逗號而不報錯

let foo = function (
                a,
                b,
                c,
            ) {
                console.log('a:', a)
                console.log('b:', b)
                console.log('c:', c)
            }
            foo(1, 3, 4, )

            //輸出結果爲:
            a: 1
            b: 3
            c: 4
複製代碼

它適用於那種多行參數而且參數名很長的狀況,開發過程當中,若是忘記刪除尾部逗號也不要緊,ES8已經支持這種寫法。

7.修飾器Decorator

ES8神器Decorator,修飾器,也稱修飾器模式

7.1 僞Decorator

在介紹Decorator以前,咱們先來實現這樣一個功能:

定義一個函數,在調用這個函數時,可以執行一些其餘額外操做

以下代碼,定義doSometing(),在調用它時再執行其餘代碼

function doSometing(name) {
            console.log('Hello' + name)
        }
        function myDecorator(fn) {
            return function() {
                console.log('start')
                const res = fn.apply(this, arguments)
                console.log('end')
                return res
            }
        }
        const wrapped = myDecorator(doSometing)
        doSometing('lindaidai')
        //Hellowlindaidai
        
        wrapped('lindaidai')
        //start 
        //Hellowlindaidai
        //end
複製代碼

能夠看到上面的操做:其實就是一個函數包裝成另外一個函數,這樣的方式咱們稱之爲「修飾器」

同理,咱們是否是能用一個什麼東西附着在咱們的類或者類的屬性上,讓它們也有一些附加的屬性或者功能呢,好比這樣:

@addSkill
class Person { }

function addSkill(target) {
    target.say = "hello world";
}
複製代碼

Person這個類中,開始定義的時候是什麼屬性都沒有的,在其上面使用@來附着上一個函數,這個函數的功能是給目標對象添加額外的屬性say

這樣Person這個類就有了say這個屬性了。

此時控制檯輸出:

console.log(Person['say']) //'hello world'
複製代碼

一樣的,若是想使用Person這個類建立出來的對象也能附加上一些屬性,能夠在目標對象的原型對象中進行添加:

@addSkill
class Person { }

function addSkill(target) {
    target.say = "hello world"; //直接添加到類中
    target.prototype.eat = "apple"; //添加到類的原型對象中
}
var personOne = new Person()

console.log(Person['say']) // 'hello world'
console.log(personOne['eat']) // 'apple'
複製代碼

上面案例中的@addSkill其實就是一個最簡單的修飾器。

固然,若是你將上面案例中的代碼複製到你html文件中,會發現它並不能如願的執行:

image.png

那是由於decorator是es7提供的方法,在瀏覽器中是沒法直接運行的,若是你想要使用它,咱們須要提早作一些準備,對它進行編譯。

若是你不想深刻其中,只是想單純的瞭解並使用它能夠參考下面的簡易教程。

7.2 快速使用

網上使用Decorator的教材有不少,大多都是要須要使用插件來讓瀏覽器支持Decorator。這裏長話短說,貼上一個最精簡的使用教程:

1.建立一個名爲:Decorator的文件夾

2.在文件夾目錄下執行命令行

npm i babel-plugin-transform-decorators-legacy babel-register --save-dev
複製代碼

此時文件夾下會出現倆個文件: node_modules 依賴文件夾和package.json-lock.json

3.建立文件 complie.js

require('babel-register')({
    plugins: ['transform-decorators-legacy']
});
require("./app.js")
複製代碼

4.建立文件 app.js

@addSkill
class Person { }
function addSkill(target) {
    target.say = "hello world";
}
console.log(Person.say)   //'hello world'
複製代碼

5.在根目錄下執行指令:

node complie.js
複製代碼

此時能夠看到命令行中打印出了 hello world

簡單介紹下上面步驟的原理:

第二步中使用了倆個基礎插件:

transform-decorators-legacy:
//是第三方插件,用於支持decorators

babel-register:
//用於接入node api
複製代碼

第三步、第四步建立的倆個文件

complie.js  //用來編譯app
app.js   //使用了裝飾器的js文件
複製代碼

第五步:

原理:
1,node執行complie.js文件;
2,complie文件改寫了node的require方法;
3,complie在引用app.js,使用了新的require方法;
4,app.js在加載過程當中被編譯,並執行。
複製代碼

固然你也能夠將app.js替換爲app.ts 不過別忘了把complie.js中的app.js修改成app.ts

// app.ts
@addSkill
class Person { }
function addSkill(target) {
    target.say = "hello world";
}
console.log(Person['say'])   
//這裏若是直接使用Person.say會提示say屬性不存在,如我使用的vscode編輯器就會報錯,是由於ts的緣由,只須要用[]的形式獲取對象屬性便可。
複製代碼

注:ts中有些語法是和js中不同的,好比有些對象上提示沒有屬性的時候,只須要換一種獲取對象屬性的方式便可。

7.3 類修飾器

直接做用在類上面的修飾器,咱們能夠稱之爲類修飾器。

如上面案例中的@addSkill就是一個類修飾器,它修改了Person這個類的行爲,爲它加上了靜態屬性say

addSkill函數的參數target是Person這個類自己。

1.修飾器的執行原理基本就是這樣:

@decorator
class A {}

// 等同於

class A {}
A = decorator(A) || A;
複製代碼

換句話說,類修飾器是一個對類進行處理的函數。

它的第一個參數target就是函數要處理的目標類。

2.多參數

固然若是你想要有多個參數也是能夠的,咱們能夠在修飾器外面再封裝一層函數:

@addSkill("hello world")
class Person { }
function addSkill(text) {
    return function(target) {
        target.say = text;
    }
}
console.log(Person.say)  //'hello world'
複製代碼

上面代碼中,修飾器addSkill能夠接受參數,這就等於能夠修改修飾器的行爲。

3.修飾器在何時執行。

先來看一個案例:

@looks
class Person { }
function looks(target) {
    console.log('I am handsome')
    target.looks = 'handsome'
}

console.log(Person['looks'])

//I am handsome
//handsome
複製代碼

在修飾器@looks中添加一個console.log()語句,卻發現它是最先執行的,其次纔打印出handsome

這是由於裝飾器對類的行爲的改變,是代碼編譯時發生的,而不是在運行時。這意味着,裝飾器能在編譯階段運行代碼。也就是說,裝飾器本質就是編譯時執行的函數。

裝飾器是在編譯時就執行的函數

7.4 方法修飾器

上面的案例中,修飾器做用的對象是類自己。

固然修飾器不只僅這麼簡單,它也能夠做用在類裏的某個方法或者屬性上,這樣的修飾器咱們稱它爲方法修飾器。

以下面的案例:

class Person {
    constructor() {}
    @myname  //方法修飾器
    name() {
        console.log('霖呆呆') 
    }
}
function myname(target, key, descriptor) {
    console.log(target);
    console.log(key);
    console.log(descriptor);
    descriptor.value = function() {
        console.log('霖呆呆')
    }
}

var personOne = new Person() //實例化
personOne.name() //調用name()方法


//打印結果:
Person {}
name
{ value: [Function: name],
  writable: true,
  enumerable: false,
  configurable: true 
 }
霖呆呆
複製代碼

上面案例中的修飾器@myname是放在name()方法上的,myname函數有三個參數:

target: 類的原型對象,上例是Person.prototype
key: 所要修飾的屬性名  name
descriptor: 該屬性的描述對象
複製代碼

咱們改變了descriptor中的value,使之打印出霖呆呆。

7.5 多個修飾器的執行順序

如果同一個方法上有多個修飾器,會像剝洋蔥同樣,先從外到內進入,而後由內向外執行。

class Person {
    constructor() {}
    @dec(1)
    @dec(2)
    name() {
        console.log('霖呆呆')
    }
}
function dec(id) {
    console.log('out', id);
    return function(target, key, descriptor) {
        console.log(id);
    }
}

var person = new Person()
person.name()
//結果
out 1
out 2
2
1
霖呆呆
複製代碼

如上所屬,外層修飾器dec(1)先進入,可是內層修飾器dec(2)先執行。

7.6 不能做用於函數

修飾器不能做用於函數之上,這是由於函數和變量同樣都會提高

var counter = 0;

var add = function () {
  counter++;
};

@add
function foo() {
}
複製代碼

如上面的例子所示,給函數foo()定義了修飾器@add,做用是想將counter++

預計的結果counter爲1,但實際上卻仍是爲0

緣由:

定義的函數foo()會被提高至最上層,定義的變量counteradd也會被提高,效果以下:

@add
function foo() {
}

var counter;
var add;

counter = 0;

add = function () {
  counter++;
};
複製代碼

總之,因爲存在函數提高,使得修飾器不能用於函數。類是不會提高的,因此就沒有這方面的問題。

另外一方面,若是必定要修飾函數,能夠採用高階函數的形式直接執行。

如在7.1中的例子所示:

function doSometing(name) {
            console.log('Hello' + name)
        }
        function myDecorator(fn) {
            return function() {
                console.log('start')
                const res = fn.apply(this, arguments)
                console.log('end')
                return res
            }
        }
        const wrapped = myDecorator(doSometing)
        doSometing('lindaidai')
        //Hellowlindaidai
        
        wrapped('lindaidai')
        //start 
        //Hellowlindaidai
        //end
複製代碼

後語

知識無價,尊重原創。

參考文集:

10分鐘學會ES7+ES8

JavaScript中的裝飾器--Decorator

es7-decorator修飾器運行環境搭建及實踐

阮一峯Decorator詳解

相關文章
相關標籤/搜索