ECMAScript 2017(ES8)新特性簡介

簡介

ES8是ECMA協會在2017年6月發行的一個版本,因爲是ECMAScript的第八個版本,所以也稱爲ES8.

今天我們講解一下ES8的新特性。

ES8引入了2大特性和4個小的特性,我們接下來一一講解。

Async函數

我們在ES6中提到了generator,Async函數的操作和generator很類似。

我們看下Async的使用:

//Async 函數定義:
async function foo() {}

//Async 函數表達式:
const foo = async function () {};

//Async 方法定義:
let obj = { async foo() {} }

//Async 箭頭函數:
const foo = async () => {};
 

async函數返回的是一個封裝的Promise對象:

async function asyncFunc() {
    return 123;
}

asyncFunc()
.then(x => console.log(x));
    // 123
 

如果在函數中拋出異常,則會reject Promise:

async function asyncFunc() {
    throw new Error('Problem!');
}

asyncFunc()
.catch(err => console.log(err));
    // Error: Problem!
 

上面的例子中我們在async函數使用的是同步的代碼,如果想要在async中執行異步代碼,則可以使用await,注意await只能在async中使用。

await後面接的是一個Promise。如果Promise完成了,那麼await被賦值的結果就是Promise的值。

如果Promise被reject了,那麼await將會拋出異常。

async function asyncFunc() {
    const result = await otherAsyncFunc();
    console.log(result);
}

// Equivalent to:
function asyncFunc() {
    return otherAsyncFunc()
    .then(result => {
        console.log(result);
    });
}
 

我們可以順序處理異步執行的結果:

async function asyncFunc() {
    const result1 = await otherAsyncFunc1();
    console.log(result1);
    const result2 = await otherAsyncFunc2();
    console.log(result2);
}

// Equivalent to:
function asyncFunc() {
    return otherAsyncFunc1()
    .then(result1 => {
        console.log(result1);
        return otherAsyncFunc2();
    })
    .then(result2 => {
        console.log(result2);
    });
}
 

也可以並行執行異步結果:

async function asyncFunc() {
    const [result1, result2] = await Promise.all([
        otherAsyncFunc1(),
        otherAsyncFunc2(),
    ]);
    console.log(result1, result2);
}

// Equivalent to:
function asyncFunc() {
    return Promise.all([
        otherAsyncFunc1(),
        otherAsyncFunc2(),
    ])
    .then([result1, result2] => {
        console.log(result1, result2);
    });
}
 

最後看下如何處理異常:

async function asyncFunc() {
    try {
        await otherAsyncFunc();
    } catch (err) {
        console.error(err);
    }
}

// Equivalent to:
function asyncFunc() {
    return otherAsyncFunc()
    .catch(err => {
        console.error(err);
    });
}
 

需要注意的是,如果async中返回的不是Promise,那麼將會被封裝成爲Promise。如果已經是Promise對象的話,則不會被再次封裝:

async function asyncFunc() {
    return Promise.resolve(123);
}
asyncFunc()
.then(x => console.log(x)) // 123
 

同樣的,如果返回一個rejected的Promise對象,則和拋出異常一樣的結果:

async function asyncFunc() {
    return Promise.reject(new Error('Problem!'));
}
asyncFunc()
.catch(err => console.error(err)); // Error: Problem!
 

如果你只是想觸發異步方法,但是並不想等待它執行完畢,那麼不使用await:

async function asyncFunc() {
    const writer = openFile('someFile.txt');
    writer.write('hello'); // don’t wait
    writer.write('world'); // don’t wait
    await writer.close(); // wait for file to close
}
 

共享內存和原子操作

ES7引入了一個新的構造函數SharedArrayBuffer和命名空間Atomics。

在JS中,除了主線程之外,我們還可以創建worker線程,主線程和worker線程之間的通信是通過postMessage方法來進行的。

但是這樣的通信方式並不高效。於是引入了SharedArrayBuffer這樣的共享空間,來提升消息傳輸效率。

// main.js

const worker = new Worker('worker.js');

// To be shared
const sharedBuffer = new SharedArrayBuffer( // (A)
    10 * Int32Array.BYTES_PER_ELEMENT); // 10 elements

// Share sharedBuffer with the worker
worker.postMessage({sharedBuffer}); // clone

// Local only
const sharedArray = new Int32Array(sharedBuffer); // (B)
 

上面的例子中,我們創建了一個SharedArrayBuffer,並將這個SharedArrayBuffer通過postMessage的方式發給worker。

我們知道postMessage是以拷貝的方式來發送消息的,但是這是正確使用共享的方式。

我看下在worker中怎麼接收這個Buffer:

// worker.js

self.addEventListener('message', function (event) {
    const {sharedBuffer} = event.data;
    const sharedArray = new Int32Array(sharedBuffer); // (A)

    // ···
});
 

在worker中,我們將sharedBuffer使用Int32Array封裝起來,作爲Array而使用。

那麼我們考慮一個問題,在使用sharedBuffer的過程中,會出現什麼問題呢?

因爲是共享的,所以可以在多個worker線程中同時被使用。如果在同時被使用的時候就會出現多線程共享數據的問題,也就是併發的問題。

爲了解決併發的問題,我們回想一下在java中特別有一個concurrent包,裏面有一些Atomic的類,可以執行原子性操作。

在ES8中,同樣引入了Atomics,用來進行SharedArrayBuffer的原子性操作。同時,使用Atomics還可以禁止重排序。

Atomics實際操作的Typed Array:Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array or Uint32Array。

注意,這些Array都是SharedArrayBuffer的封裝Array。並且都是Int的Array(目前只支持Int類型)。

首先看下Atomics怎麼解決數組的併發寫入和讀取的問題:

Atomics.load(ta : TypedArray<T>, index) : T
Atomics.store(ta : TypedArray<T>, index, value : T) : T
Atomics.exchange(ta : TypedArray<T>, index, value : T) : T
Atomics.compareExchange(ta : TypedArray<T>, index, expectedValue, replacementValue) : T
 

load和store可以將ta作爲一個整體來操作。

看下使用例子:

// main.js
console.log('notifying...');
Atomics.store(sharedArray, 0, 123);

// worker.js
while (Atomics.load(sharedArray, 0) !== 123) ;
console.log('notified');
 

Atomics還提供了wait和notity功能:

Atomics.wait(ta: Int32Array, index, value, timeout)
Atomics.wake(ta : Int32Array, index, count)
 

當ta[index]的值是value的時候,wait將會使worker等待在ta[index]之上。

而wake,則是將等待在ta[index]上的count個worker喚醒。

Atomics還提供了一系列的操作:

Atomics.add(ta : TypedArray<T>, index, value) : T
Atomics.sub(ta : TypedArray<T>, index, value) : T
Atomics.and(ta : TypedArray<T>, index, value) : T
Atomics.or(ta : TypedArray<T>, index, value) : T
Atomics.xor(ta : TypedArray<T>, index, value) : T
 

它相當於:

ta[index] += value;
 

Atomic有一個很棒的作用就是構建lock。我們將會在後面的文章中介紹。

Object的新方法

Object提供了兩個遍歷的新方法entries和values。

Object.entries(value : any) : Array<[string,any]>
 

entries返回的是一個數組,裏面存儲的是key-value對:

> Object.entries({ one: 1, two: 2 })
[ [ 'one', 1 ], [ 'two', 2 ] ]
 

entries給了我們一個遍歷Object的方法:

let obj = { one: 1, two: 2 };
for (let [k,v] of Object.entries(obj)) {
    console.log(`{JSON.stringify(k)}:{JSON.stringify(v)}`);
}
// Output:
// "one": 1
// "two": 2
 

我們可以使用entries來創建Map:

let map = new Map(Object.entries({
    one: 1,
    two: 2,
}));
console.log(JSON.stringify([...map]));
    // [["one",1],["two",2]]
 

同樣的,Object還提供了values方法:

Object.values(value : any) : Array<any>
 

返回的是一個數組,數組中存放的是Object的value。

除此之外,Object還有一個getOwnPropertyDescriptors新方法。

這個方法返回的是Obj中的屬性的描述。所謂屬性的描述就是指這個屬性是否可寫,是否可以數之類:

const obj = {
    [Symbol('foo')]: 123,
    get bar() { return 'abc' },
};
console.log(Object.getOwnPropertyDescriptors(obj));

// Output:
// { [Symbol('foo')]:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }
 

key是Obj中的key,value就是PropertyDescriptors。

雖然在ES6中,Obj已經引入了一個Object.assign()方法用來拷貝properties,但是這個assign只能拷貝具有默認屬性值的屬性。對於那些具有非默認屬性值的屬性getters, setters, non-writable properties來說,Object.assign是不能拷貝的。這個時候就需要使用getOwnPropertyDescriptors方法了。

const source = {
    set foo(value) {
        console.log(value);
    }
};
console.log(Object.getOwnPropertyDescriptor(source, 'foo'));
// { get: undefined,
//   set: [Function: foo],
//   enumerable: true,
//   configurable: true }

const target1 = {};
Object.assign(target1, source);
console.log(Object.getOwnPropertyDescriptor(target1, 'foo'));
// { value: undefined,
//   writable: true,
//   enumerable: true,
//   configurable: true }
 

可以看到obj就有一個foo屬性,它是一個setter。所以使用assign是不能進行拷貝的。

我們看下怎麼使用defineProperties和getOwnPropertyDescriptors來進行拷貝:

const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
console.log(Object.getOwnPropertyDescriptor(target2, 'foo'));
// { get: undefined,
//   set: [Function: foo],
//   enumerable: true,
//   configurable: true }
 

除了拷貝屬性之外,我們還可以拷貝對象:

const clone = Object.create(Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj));
 

String的新方法

String添加了兩個新的方法padStart和padEnd。

pad就是填充的意思,我們可以從前面填充也可以從後面填充。我們看下pad的用法:

String.prototype.padStart(maxLength, fillString=' ') 
String.prototype.padEnd(maxLength, fillString=' ') 
 

看下具體的使用:

> 'x'.padStart(5, 'ab')
'ababx'
> 'x'.padEnd(5, 'ab')
'xabab'
 

逗號可以添加到函數的參數列表後面了

在ES8之前,函數的最後一個參數是不允許添加逗號的,但是在ES8中,一切都變得可能。

function foo(
    param1,
    param2,
) {}
 

我們可以在函數的定義中添加逗號。也可以在函數的調用中添加逗號:

foo(
    'abc',
    'def',
);
 

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/ecmascript-8/

本文來源:flydean的博客

歡迎關注我的公衆號:「程序那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章