1. 程式人生 > 實用技巧 >ES6、ES7、ES8

ES6、ES7、ES8

ES6

https://es6.ruanyifeng.com/

ES7

1.Array.prototype.includes()

includes()作用,是查詢一個值在不在數組裡,若是存在則返回true,不存在返回false.

1.基本用法:

['a', 'b', 'c'].includes('a')     // true
['a', 'b', 'c'].includes('d')     // false

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

['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()比較

有些時候是等效的

['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

優缺點比較

  • 簡便性

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

  • 精確性

    兩者都是採用===的操作符來作比較的,不同之處在於:對於NaN的處理結果不同。

    我們知道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
  }
}

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')
        //Hello lindaidai
        
        wrapped('lindaidai')
        //start 
        //Hello lindaidai
        //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


作者:LinDaiDai_霖呆呆
連結:https://www.jianshu.com/p/13c5d002478b
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

ES7

1.Array.prototype.includes()

includes()作用,是查詢一個值在不在數組裡,若是存在則返回true,不存在返回false.

1.基本用法:

['a', 'b', 'c'].includes('a')     // true
['a', 'b', 'c'].includes('d')     // false

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

['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()比較

有些時候是等效的

['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

優缺點比較

  • 簡便性

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

  • 精確性

    兩者都是採用===的操作符來作比較的,不同之處在於:對於NaN的處理結果不同。

    我們知道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: 'tsx',
        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: 'tsx',
    writable: true
  }
}

5.2與getOwnPropertyDescriptor()比較

ES6中也有一個返回目標物件可列舉屬性的方法

var obj = {
    id: 1,
    name: 'tsx',
    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')
        //Hello lindaidai
        
        wrapped('lindaidai')
        //start 
        //Hello lindaidai
        //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('tsx') 
    }
}
function myname(target, key, descriptor) {
    console.log(target);
    console.log(key);
    console.log(descriptor);
    descriptor.value = function() {
        console.log('tsx')
    }
}

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,使之打印出tsx。

7.5 多個修飾器的執行順序

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

class Person {
    constructor() {}
    @dec(1)
    @dec(2)
    name() {
        console.log('tsx')
    }
}
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
tsx

如上所屬,外層修飾器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