1. 程式人生 > >ECMAScript 2017(ES8)新特性簡介

ECMAScript 2017(ES8)新特性簡介

[toc] # 簡介 ES8是ECMA協會在2017年6月發行的一個版本,因為是ECMAScript的第八個版本,所以也稱為ES8. 今天我們講解一下ES8的新特性。 ES8引入了2大特性和4個小的特性,我們接下來一一講解。 # Async函式 我們在ES6中提到了generator,Async函式的操作和generator很類似。 我們看下Async的使用: ~~~js //Async 函式定義: async function foo() {} //Async 函式表示式: const foo = async function () {}; //Async 方法定義: let obj = { async foo() {} } //Async 箭頭函式: const foo = async () => {}; ~~~ async函式返回的是一個封裝的Promise物件: ~~~js async function asyncFunc() { return 123; } asyncFunc() .then(x => console.log(x)); // 123 ~~~ 如果在函式中丟擲異常,則會reject Promise: ~~~js 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將會丟擲異常。 ~~~js async function asyncFunc() { const result = await otherAsyncFunc(); console.log(result); } // Equivalent to: function asyncFunc() { return otherAsyncFunc() .then(result => { console.log(result); }); } ~~~ 我們可以順序處理非同步執行的結果: ~~~js 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); }); } ~~~ 也可以並行執行非同步結果: ~~~js 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); }); } ~~~ 最後看下如何處理異常: ~~~js 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物件的話,則不會被再次封裝: ~~~js async function asyncFunc() { return Promise.resolve(123); } asyncFunc() .then(x => console.log(x)) // 123 ~~~ 同樣的,如果返回一個rejected的Promise物件,則和丟擲異常一樣的結果: ~~~js async function asyncFunc() { return Promise.reject(new Error('Problem!')); } asyncFunc() .catch(err => console.error(err)); // Error: Problem! ~~~ 如果你只是想觸發非同步方法,但是並不想等待它執行完畢,那麼不使用await: ~~~js 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這樣的共享空間,來提升訊息傳輸效率。 ~~~js // 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: ~~~js // 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怎麼解決陣列的併發寫入和讀取的問題: ~~~js Atomics.load(ta : TypedArray, index) : T Atomics.store(ta : TypedArray, index, value : T) : T Atomics.exchange(ta : TypedArray, index, value : T) : T Atomics.compareExchange(ta : TypedArray, index, expectedValue, replacementValue) : T ~~~ load和store可以將ta作為一個整體來操作。 看下使用例子: ~~~js // main.js console.log('notifying...'); Atomics.store(sharedArray, 0, 123); // worker.js while (Atomics.load(sharedArray, 0) !== 123) ; console.log('notified'); ~~~ Atomics還提供了wait和notity功能: ~~~js 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還提供了一系列的操作: ~~~js Atomics.add(ta : TypedArray, index, value) : T Atomics.sub(ta : TypedArray, index, value) : T Atomics.and(ta : TypedArray, index, value) : T Atomics.or(ta : TypedArray, index, value) : T Atomics.xor(ta : TypedArray, index, value) : T ~~~ 它相當於: ~~~js ta[index] += value; ~~~ Atomic有一個很棒的作用就是構建lock。我們將會在後面的文章中介紹。 # Object的新方法 Object提供了兩個遍歷的新方法entries和values。 ~~~js Object.entries(value : any) : Array<[string,any]> ~~~ entries返回的是一個數組,裡面儲存的是key-value對: ~~~js > Object.entries({ one: 1, two: 2 }) [ [ 'one', 1 ], [ 'two', 2 ] ] ~~~ entries給了我們一個遍歷Object的方法: ~~~js 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: ~~~js let map = new Map(Object.entries({ one: 1, two: 2, })); console.log(JSON.stringify([...map])); // [["one",1],["two",2]] ~~~ 同樣的,Object還提供了values方法: ~~~js Object.values(value : any) : Array ~~~ 返回的是一個數組,陣列中存放的是Object的value。 除此之外,Object還有一個getOwnPropertyDescriptors新方法。 這個方法返回的是Obj中的屬性的描述。所謂屬性的描述就是指這個屬性是否可寫,是否可以數之類: ~~~js 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方法了。 ~~~js 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來進行拷貝: ~~~js const target2 = {}; Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source)); console.log(Object.getOwnPropertyDescriptor(target2, 'foo')); // { get: undefined, // set: [Function: foo], // enumerable: true, // configurable: true } ~~~ 除了拷貝屬性之外,我們還可以拷貝物件: ~~~js const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ~~~ # String的新方法 String添加了兩個新的方法padStart和padEnd。 pad就是填充的意思,我們可以從前面填充也可以從後面填充。我們看下pad的用法: ~~~js String.prototype.padStart(maxLength, fillString=' ') String.prototype.padEnd(maxLength, fillString=' ') ~~~ 看下具體的使用: ~~~js > 'x'.padStart(5, 'ab') 'ababx' > 'x'.padEnd(5, 'ab') 'xabab' ~~~ # 逗號可以新增到函式的引數列表後面了 在ES8之前,函式的最後一個引數是不允許新增逗號的,但是在ES8中,一切都變得可能。 ~~~js function foo( param1, param2, ) {} ~~~ 我們可以在函式的定義中新增逗號。也可以在函式的呼叫中新增逗號: ~~~js foo( 'abc', 'def', ); ~~~ > 本文作者:flydean程式那些事 > > 本文連結:[http://www.flydean.com/ecmascript-8/](http://www.flydean.com/ecmascript-8/) > > 本文來源:flydean的部落格 > > 歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來