1. 程式人生 > >ECMAScript 2018(ES9)新特性簡介

ECMAScript 2018(ES9)新特性簡介

[toc] # 簡介 ES9是ECMA協會在2018年6月發行的一個版本,因為是ECMAScript的第九個版本,所以也稱為ES9. 今天我們講解一下ES9的新特性。 ES9引入了3大特性和2個小的特性,我們接下來一一講解。 # 非同步遍歷 在ES6中,引入了同步iteration的概念,隨著ES8中的Async操作符的引用,在ES9中引入了非同步遍歷的新特性Async iteration。 具體的內容可以參考我之前的文章 [ES9的新特性:非同步遍歷Async iteration]() # Rest/Spread操作符和物件構建 Rest和Spread的操作符都是 ... , 只不過使用的場景和目的不一樣。 rest主要用在物件的解構,目前只支援物件的解構和不確定的引數描述。 Spread主要用在字面量物件的構建上。 下面我們分別來介紹: ## Rest 如果用在物件的解構中,除了已經手動指定的屬性名之外,rest將會拷貝物件其他的所有可列舉(enumerable)的屬性。 ~~~js const obj = {foo: 1, bar: 2, baz: 3}; const {foo, ...rest} = obj; // Same as: // const foo = 1; // const rest = {bar: 2, baz: 3}; ~~~ 如果用在引數中,rest表示的是所有剩下的引數: ~~~js function func({param1, param2, ...rest}) { // rest operator console.log('All parameters: ', {param1, param2, ...rest}); // spread operator return param1 + param2; } ~~~ 注意,在Obj字面量中,rest運算子只能放在obj的最頂層,並且只能使用一次,還要放在最後。 ~~~js const {...rest, foo} = obj; // SyntaxError const {foo, ...rest1, ...rest2} = obj; // SyntaxError ~~~ 當然你還可以巢狀使用rest運算子: ~~~js const obj = { foo: { a: 1, b: 2, c: 3, }, bar: 4, baz: 5, }; const {foo: {a, ...rest1}, ...rest2} = obj; // Same as: // const a = 1; // const rest1 = {b: 2, c: 3}; // const rest2 = {bar: 4, baz: 5}; ~~~ ## Spread spread主要被用來展開物件,能夠被展開物件的屬性一定要是可列舉的enumerable。 ~~~js > const obj = {foo: 1, bar: 2}; > {...obj, baz: 3} { foo: 1, bar: 2, baz: 3 } ~~~ 如果物件的屬性key一樣,那麼後面屬性值會覆蓋之前的屬性值: ~~~js > const obj = {foo: 1, bar: 2, baz: 3}; > {...obj, foo: true} { foo: true, bar: 2, baz: 3 } > {foo: true, ...obj} { foo: 1, bar: 2, baz: 3 } ~~~ ## 建立和拷貝物件 使用Object.assign和Spread操作符可以很方便的進行物件的拷貝。 我們看一個最簡單的物件拷貝的例子: ~~~js const clone1 = {...obj}; const clone2 = Object.assign({}, obj); ~~~ 但是這樣的拷貝有個缺點,就是隻能拷貝自有的可列舉的屬性。 並且拷貝之後物件的prototypes是Object.prototype,也就是說沒有繼承被拷貝物件的prototype。 ~~~js > Object.getPrototypeOf(clone1) === Object.prototype true > Object.getPrototypeOf(clone2) === Object.prototype true > Object.getPrototypeOf({}) === Object.prototype true ~~~ 如果想要同時拷貝物件的prototype,則可以這樣做: ~~~js const clone1 = {__proto__: Object.getPrototypeOf(obj), ...obj}; const clone2 = Object.assign( Object.create(Object.getPrototypeOf(obj)), obj); ~~~ 或者指定物件內建的__proto__屬性,或者從obj的prtotype建立一個新的物件。 > 注意,物件內建的__proto__屬性只在部分瀏覽器中支援。 Object.assign和spread只能拷貝可列舉的屬性,如果是set,get屬性或者想要拷貝屬性的attributes(writable, enumerable),那麼就需要用到我們之前講到的Object.getOwnPropertyDescriptors。 ~~~js const clone1 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj)); const clone2 = Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ~~ > 注意,我們使用的所有的拷貝都是淺拷貝。如果被拷貝的物件內部又有物件的話,拷貝的只是這個物件的引用 ~~~js const original = { prop: {} }; const clone = Object.assign({}, original); console.log(original.prop === clone.prop); // true original.prop.foo = 'abc'; console.log(clone.prop.foo); // abc ~~~ ## Spread和bject.assign() 的區別 assgin在拷貝物件的時候,會呼叫相應屬性的set方法,而spread不會。 舉個例子,我們先給Object.prototype定義一個set方法: ~~~js Object.defineProperty(Object.prototype, 'foo', { set(value) { console.log('SET', value); }, }); const obj = {foo: 123}; ~~~ 然後看一下拷貝的區別: ~~~js > Object.assign({}, obj) SET 123 {} > { ...obj } { foo: 123 } ~~~ 可以看到assign會觸發set方法,而spread不會。 另外,如果物件屬性是不可寫的,那麼assign將會報錯,而spread不會。 我們先定義一個不可寫的物件: ~~~js Object.defineProperty(Object.prototype, 'bar', { writable: false, value: 'abc', }); ~~~ 看下賦值操作: ~~~js > const tmp = {}; > tmp.bar = 123; TypeError: Cannot assign to read only property 'bar' > Object.assign({}, obj) TypeError: Cannot assign to read only property 'bar' > { ...obj } { bar: 123 } ~~~ # 正則表示式 ES9的正則表示式新特性可以參考我的文章 [ES9的新特性:正則表示式RegExp]() # promise.finally promise除了then和catch方法之外,還引入了新的finally方法。 和java中的finally一樣,promise.finally一定會被執行。 ~~~js promise .then(result => {···}) .catch(error => {···}) .finally(() => {···}); ~~~ 和java一樣,我們可以在finally中做一些資源清理的工作: ~~~js let connection; db.open() .then(conn => { connection = conn; return connection.select({ name: 'Jane' }); }) .then(result => { ... }) ··· .catch(error => { // handle errors }) .finally(() => { connection.close(); }); ~~~ 上面的例子中,我們開啟了一個數據庫的連線,在使用完之後,我們在finally中對其進行close操作。 # 模板文字和帶標籤的模板文字 模板文字和帶標籤的模板文字是在ES6中引入的,在ES9中進行了修正。 我們先看下什麼是模本文字,模板文字(Template literals)就是在反引號中輸入的文字,在其中可以使用${···})來進行變數的解析,並且還支援回車換行。 ~~~js const firstName = 'Jane'; console.log(`Hello ${firstName}! How are you today?`); // Output: // Hello Jane! // How are you // today? ~~~ 而帶標籤的模板文字是指在模板文字之前放上一個函式呼叫: ~~~js String.raw`\u{4B}` '\u{4B}' ~~~ 這裡String.raw被稱為tag function,我們看下raw的定義: ~~~js raw(template: TemplateStringsArray, ...substitutions: any[]): string; ~~~ 上面的程式碼還可以改寫為: ~~~js String.raw`\u004B` '\u004B' ~~~ `\u{4B}`和 `\u004B` 都是字元K的unicode表示。 上面的raw其實可以這樣表示: ~~~js function tagFunc(tmplObj, substs) { return { Cooked: tmplObj, Raw: tmplObj.raw, }; } ~~~ 我們可以這樣使用: ~~~js > tagFunc`\u{4B}`; { Cooked: [ 'K' ], Raw: [ '\\u{4B}' ] } ~~~ > 本文作者:flydean程式那些事 > > 本文連結:[http://www.flydean.com/ecmascript-9/](http://www.flydean.com/ecmascript-9/) > > 本文來源:flydean的部落格 > > 歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來