ES9已經來了 Are you ready?

LinDaiDai_霖呆呆發表於2018-08-06

ES9

前言

該篇文章主要是介紹了ES9新加的一些特性。

1. 非同步迭代

async/await的某些時刻,你可能嘗試在同步迴圈中呼叫非同步函式。例如:

async function process(array) {
  for (let i of array) {
    await doSomething(i);
  }
}
複製程式碼
複製程式碼

這段程式碼不會正常執行,下面這段同樣也不會:

async function process(array) {
  array.forEach(async i => {
    await doSomething(i);
  });
}
複製程式碼
複製程式碼

這段程式碼中,迴圈本身依舊保持同步,並在在內部非同步函式之前全部呼叫完成。

ES2018引入非同步迭代器(asynchronous iterators),這就像常規迭代器,除了next()方法返回一個Promise。因此await可以和for...of迴圈一起使用,以序列的方式執行非同步操作。例如:

async function process(array) {
  for await (let i of array) {
    doSomething(i);
  }
}
複製程式碼

2. Promise.finally()

在ES6中,一個Promise鏈要麼成功進入最後一個then()要麼失敗觸發catch()。而實際中,我們可能需要無論Promise無論成功還是失敗,都執行相同的程式碼。例如清除,刪除回話,關閉資料庫連線等操作。

ES9中,允許使用finally()來指定最終的邏輯。

如下:

        let count = () => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(100)
                }, 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)
        })
        .finally(() => {
            console.log('finally')
        }) 
        
        //執行結果
        async
        {count: 100, list: [1, 2, 3]}
        finally
複製程式碼

3. Rest/Spread 屬性

3.1 ES6中(...)

在ES6中引入了三點...,作用主要是Rest引數和擴充套件運算子:

作用物件僅用於陣列

1.將一個未知數量的參數列示一個陣列:

restParam(1, 2, 3, 4, 5);

function restParam(p1, p2, ...p3) {
  // p1 = 1
  // p2 = 2
  // p3 = [3, 4, 5]
}
複製程式碼

2.擴充套件運算子:

const values = [99, 100, -1, 48, 16];
console.log( Math.max(...values) ); // 100
複製程式碼

3.2 ES9中(...)

在ES9中為物件提供了像陣列一樣的Rest引數和展開運算子。

Rest引數用法

        var obj = {
            a: 1,
            b: 2,
            c: 3
        }
        const { a, ...param } = obj;
        console.log(a)     //1
        console.log(param) //{b: 2, c: 3}
        
複製程式碼

Spread用法,用於收集所有的剩餘引數:

        var obj = {
            a: 1,
            b: 2,
            c: 3
        }
		function foo({a, ...param}) {
            console.log(a);
            console.log(param)
        }
複製程式碼

跟陣列一樣,Rest引數只能在宣告的結尾處使用。此外,它只適用於每個物件的頂層,如果物件中巢狀物件則無法適用。

擴充套件運算子可以在其他物件內使用

const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { ...obj1, z: 26 };
// obj2 is { a: 1, b: 2, c: 3, z: 26 }
複製程式碼

3.3 Spread的使用場景

1.淺拷貝

可以利用(...)來進行一個物件的拷貝,但是這種拷貝只能拷貝物件的可列舉自有屬性。

        var obj = {
            name: 'LinDaiDai',
            looks: 'handsome',
            foo() {
                console.log('old');
            },
            set setLooks(newVal) {
                this.looks = newVal
            },
            get getName() {
                console.log(this.name)
            }
        }

        var cloneObj = {...obj};
        cloneObj.foo = function() {
            console.log('new')
        };
        console.log(obj)     
        // { name: 'LinDaiDai',looks: 'handsome', foo: f foo(), get getName:f getName(), set setLooks: f setLooks(newVal)}
        console.log(cloneObj)
        // { name: 'LinDaiDai',looks: 'handsome', foo: f foo(), getName: undefined, setLooks: undefined }
        obj.foo()
        // old
        cloneObj.foo()
        // new 
複製程式碼

如上所示,定義了一個物件obj 並使用(...)進行物件的拷貝,修改物件內的函式foo(),並不會影響原有的物件,但是原有物件的settergetter卻不能拷貝過去。

2.合併倆個物件

const merged = {...obj1, ...obj2};
//同:
const merged = Object.assign({}, obj1, obj2);
複製程式碼

4. 正規表示式命名捕獲組

4.1 基本用法

Javascript正規表示式中使用exec()匹配能夠返回一個物件,一個包含匹配字串的類陣列。

如下面案例中的匹配日期格式:

//正規表示式命名捕獲組
        const reDate = /(\d{4})-(\d{2})-(\d{2})/,
              match = reDate.exec('2018-08-06');
        console.log(match);
        // [2018-08-06, 2018, 08, 06]
        
        // 這樣就可以直接用索引來獲取年月日:
        match[1] // 2018
        match[2] // 08
        match[3] // 06
複製程式碼

返回一個陣列,陣列第0項為與正規表示式相匹配的文字,第 1 個元素是與 RegExpObject 的第 1 個子表示式相匹配的文字(如果有的話),第 2 個元素是與 RegExpObject 的第 2 個子表示式相匹配的文字(如果有的話),以此類推。

上面的案例,若是改變正規表示式的結構就有可能改變匹配物件的索引。

如進行如下修改:

//正規表示式命名捕獲組
        const reDate = /(\d{2})-(\d{2})-(\d{4})/,
              match = reDate.exec('2018-08-06');
        console.log(match);
        // [2018-08-06, 08, 06, 2018]
        
        // 但此時年月日的索引就改變了
        match[3] // 2018
        match[1] // 08
        match[2] // 06
複製程式碼

可以看到上面寫法的弊端,因此在ES9中允許命名捕獲組使用符號?<name>,如下:

        const reDate = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
              match = reDate.exec('2018-08-06')
        console.log(match);
        // [2018-08-06, 08, 06, 2018, groups: {day: 06, month: 08, year: 2018}]
        
        //此時可以使用groups物件來獲取年月日
        match.groups.year // 2018
        match.groups.month // 08
        match.groups.day  // 06
複製程式碼

命名捕獲組的寫法相當於是把每個匹配到的捕獲組都定義了一個名字,然後儲存到返回值的groups屬性中。

4.2 結合replace()

命名捕獲也可以使用在replace()方法中。例如將日期轉換為美國的 MM-DD-YYYY 格式:

const reDate = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
      d = '2018-08-06'
      USADate = d.replace(reDate, '$<month>-$<day>-$<year>');
console.log(USADate);
// 08-06-2018
複製程式碼

還可以將中文名的姓和名調換:

const reName = /(?<sur>[a-zA-Z]+)-(?<name>[a-zA-Z]+)/;
      Chinese = 'Lin-DaiDai',
      USA = Chinese.replace(reName, '$<name>-$<sur>');
console.log(USA);
// DaiDai-Lin
複製程式碼

5. 正規表示式反向斷言

5.1 基本用法

先來看下正規表示式先行斷言是什麼:

如獲取貨幣的符號

        const noReLookahead = /\D(\d+)/,
        	  reLookahead = /\D(?=\d+)/,
        	  match1 = noReLookahead.exec('$123.45'),
              match2 = reLookahead.exec('$123.45');
        console.log(match1[0]); // $123   
        console.log(match2[0]); // $
複製程式碼

可以看到若是在正規表示式中加入?=的話,匹配會發生,但不會有任何捕獲,並且斷言沒有包含在整個匹配欄位中。

在ES9中可以允許反向斷言:

        const reLookahead = /(?<=\D)[\d\.]+/;
              match = reLookahead.exec('$123.45');
        console.log(match[0]); // 123.45
複製程式碼

使用?<=進行反向斷言,可以使用反向斷言獲取貨幣的價格,而忽略貨幣符號。

5.2 肯定反向斷言

上面的案例為肯定反向斷言,也就是說\D這個條件必須存在,若是:

        const reLookahead = /(?<=\D)[\d\.]+/;
              match1 = reLookahead.exec('123.45'),
              match2 = reLookahead.exec('12345');
        console.log(match1[0]); // 45
        console.log(match2);  // null
複製程式碼

可以看到match1匹配到的是45,這是由於在123前面沒有任何符合\D的匹配內容,它會一直找到符合\D的內容,也就是.然後返回後面的內容。

而若是沒有滿足前面肯定反向斷言的條件的話,則返回null.

6. 正規表示式dotAll模式

正規表示式中點.匹配除回車外的任何單字元,標記s改變這種行為,允許行終止符的出現:

/hello.world/.test('hello\nworld');  // false

/hello.world/s.test('hello\nworld'); // true

console.log(/hello.world/s.test(`hello
world`))   // true
複製程式碼

7. 正規表示式 Unicode 轉義

到目前為止,在正規表示式中本地訪問 Unicode 字元屬性是不被允許的。ES2018新增了 Unicode 屬性轉義——形式為\p{...}\P{...},在正規表示式中使用標記 u (unicode) 設定,在\p塊兒內,可以以鍵值對的方式設定需要匹配的屬性而非具體內容。

    const reGreekSymbol = /\p{Script=Greek}/u;
    console.log(reGreekSymbol.test('π')); // true
複製程式碼

Greek為希臘語的意思。

8. 非轉義序列的模板字串

最後,ES2018 移除對 ECMAScript 在帶標籤的模版字串中轉義序列的語法限制。

之前,\u開始一個 unicode 轉義,\x開始一個十六進位制轉義,\後跟一個數字開始一個八進位制轉義。這使得建立特定的字串變得不可能,例如Windows檔案路徑 C:\uuu\xxx\111。更多細節參考模板字串

後語

智慧財產權無價,支援原創。

參考文章:

[譯] ES2018(ES9)的新特性

【譯】ES2018 新特性:Rest/Spread 特性

ES6/ES7/ES8/ES9 可以看到javascript並沒有讓我們失望,這幾年的更新沒有落下,不間斷的學習,才能保證自己被這個社會淘汰...

相關文章