ECMAScript標準的第九版,官方稱爲ECMAScript2018(或簡稱ES2018),於2018年6月發佈。從ES2016開始,ECMAScript規範的新版本每一年發佈而不是每幾年發佈,而且添加的功能少於主要版本之前。該標準的最新版本經過添加四個新RegExp功能,休息/傳播屬性,異步迭代和,繼續每一年發佈週期Promise.prototype.finally。此外,ES2018從標記模板中刪除了轉義序列的語法限制。
其他/傳播屬性程序員
ES2015最有趣的功能之一是傳播運營商。該運算符使複製和合並數組變得更加簡單。您可使用運算符,而不是調用concat()or slice()方法...:正則表達式
const arr1 = [10, 20, 30]; // make a copy of arr1 const copy = [...arr1]; console.log(copy); // → [10, 20, 30] const arr2 = [40, 50]; // merge arr2 with arr1 const merge = [...arr1, ...arr2]; console.log(merge); // → [10, 20, 30, 40, 50]
在必須做爲函數的單獨參數傳入數組的狀況下,擴展運算符也派上用場。例如:編程
const arr = [10, 20, 30] // equivalent to // console.log(Math.max(10, 20, 30)); console.log(Math.max(...arr)); // → 30
ES2018經過向對象文字添加擴展屬性來進一步擴展此語法。使用spread屬性,能夠將對象的自身可枚舉屬性複製到新對象上。請考慮如下示例:ubuntu
const obj1 = { a: 10, b: 20 }; const obj2 = { ...obj1, c: 30 }; console.log(obj2); // → {a: 10, b: 20, c: 30}
在此代碼中,...運算符用於檢索屬性obj1並將其分配給obj2。在ES2018以前,嘗試這樣作會引起錯誤。若是有多個具備相同名稱的屬性,則將使用最後一個屬性:數組
const obj1 = { a: 10, b: 20 }; const obj2 = { ...obj1, a: 30 }; console.log(obj2); // → {a: 30, b: 20}
Spread屬性還提供了一種合併兩個或多個對象的新方法,能夠將其用做方法的替代Object.assign()方法:promise
const obj1 = {a: 10}; const obj2 = {b: 20}; const obj3 = {c: 30}; // ES2018 console.log({...obj1, ...obj2, ...obj3}); // → {a: 10, b: 20, c: 30} // ES2015 console.log(Object.assign({}, obj1, obj2, obj3)); // → {a: 10, b: 20, c: 30}
但請注意,傳播屬性並不老是產生相同的結果Object.assign()。請考慮如下代碼:瀏覽器
Object.defineProperty(Object.prototype, 'a', { set(value) { console.log('set called!'); } }); const obj = {a: 10}; console.log({...obj}); // → {a: 10} console.log(Object.assign({}, obj)); // → set called! // → {}
在此代碼中,該Object.assign()方法執行繼承的setter屬性。相反,傳播屬性徹底忽略了設置者。異步
重要的是要記住,spread屬性只複製可枚舉的屬性。在如下示例中,type屬性不會顯示在複製的對象中,由於其enumerable屬性設置爲false:async
const car = { color: 'blue' }; Object.defineProperty(car, 'type', { value: 'coupe', enumerable: false }); console.log({...car}); // → {color: "blue"}
即便它們是可枚舉的,也會忽略繼承的屬性:函數
const car = { color: 'blue' }; const car2 = Object.create(car, { type: { value: 'coupe', enumerable: true, } }); console.log(car2.color); // → blue console.log(car2.hasOwnProperty('color')); // → false console.log(car2.type); // → coupe console.log(car2.hasOwnProperty('type')); // → true console.log({...car2}); // → {type: "coupe"}
在此代碼中,car2繼承color屬性car。由於spread屬性只複製對象的屬性,color因此不包含在返回值中。
請記住,spread屬性只能生成對象的淺表副本。若是屬性包含對象,則僅複製對象的引用:
const obj = {x: {y: 10}}; const copy1 = {...obj}; const copy2 = {...obj}; console.log(copy1.x === copy2.x); // → true
該x物業copy1是指在內存中的同一對象x中copy2是指,因此全等運算的回報true。
ES2015增長的另外一個有用功能是休息參數,它使JavaScript程序員可使用它...來表示值做爲數組。例如:
const arr = [10, 20, 30]; const [x, ...rest] = arr; console.log(x); // → 10 console.log(rest); // → [20, 30]
這裏,第一個項目arr被分配給x,而剩餘的元素被分配給rest變量。這種稱爲陣列解構的模式變得如此受歡迎
const obj = { a: 10, b: 20, c: 30 }; const {a, ...rest} = obj; console.log(a); // → 10 console.log(rest); // → {b: 20, c: 30}
此代碼使用解構分配中的其他屬性將剩餘的自身可枚舉屬性複製到新對象中。請注意,rest屬性必須始終顯示在對象的末尾,不然會引起錯誤:
const obj = { a: 10, b: 20, c: 30 }; const {...rest, a} = obj; // → SyntaxError: Rest element must be last element
還要記住,在對象中使用多個rest語法會致使錯誤,除非它們是嵌套的:
const obj = { a: 10, b: { x: 20, y: 30, z: 40 } }; const {b: {x, ...rest1}, ...rest2} = obj; // no error const {...rest, ...rest2} = obj; // → SyntaxError: Rest element must be last element
Node.js的:
8.0.0(須要--harmony運行時標誌)
8.3.0(全力支持)
異步迭代
迭代數據集是編程的重要部分。此前ES2015,提供的JavaScript語句如for,for...in和while,和如方法map(),filter()以及forEach()用於此目的。爲了使程序員可以一次一個地處理集合中的元素,ES2015引入了迭代器接口。
若是對象具備Symbol.iterator屬性,則該對象是可迭代的。在ES2015,字符串和集合對象,例如Set,Map和Array用來Symbol.iterator屬性,所以是可迭代。如下代碼給出瞭如何一次訪問可迭代元素的示例:
const arr = [10, 20, 30]; const iterator = arr[Symbol.iterator](); console.log(iterator.next()); // → {value: 10, done: false} console.log(iterator.next()); // → {value: 20, done: false} console.log(iterator.next()); // → {value: 30, done: false} console.log(iterator.next()); // → {value: undefined, done: true}
Symbol.iterator是一個衆所周知的符號,指定一個返回迭代器的函數。與迭代器交互的主要方法是next()方法。此方法返回具備兩個屬性的對象:value和done。該value屬性包含集合中下一個元素的值。該done屬性包含true或false表示集合的結尾是否已到達。
默認狀況下,普通對象不可迭代,但若是Symbol.iterator在其上定義屬性,則它能夠變爲可迭代,以下例所示:
const collection = { a: 10, b: 20, c: 30, [Symbol.iterator]() { const values = Object.keys(this); let i = 0; return { next: () => { return { value: this[values[i++]], done: i > values.length } } }; } }; const iterator = collection[Symbol.iterator](); console.log(iterator.next()); // → {value: 10, done: false} console.log(iterator.next()); // → {value: 20, done: false} console.log(iterator.next()); // → {value: 30, done: false} console.log(iterator.next()); // → {value: undefined, done: true}
此對象是可迭代的,由於它定義了一個Symbol.iterator屬性。迭代器使用該Object.keys()方法獲取對象屬性名稱的數組,而後將其分配給values常量。它還定義了一個計數器變量並給它一個初始值0.當執行迭代器時,它返回一個包含next()方法的對象。每次next()調用該方法時,它都會返回一{value, done}對,並value保持集合中的下一個元素並done保持一個布爾值,指示迭代器是否已達到集合的須要。
雖然這段代碼天衣無縫,但卻沒必要要地複雜化。幸運的是,使用生成器函數能夠大大簡化過程:
const collection = { a: 10, b: 20, c: 30, [Symbol.iterator]: function * () { for (let key in this) { yield this[key]; } } }; const iterator = collection[Symbol.iterator](); console.log(iterator.next()); // → {value: 10, done: false} console.log(iterator.next()); // → {value: 20, done: false} console.log(iterator.next()); // → {value: 30, done: false} console.log(iterator.next()); // → {value: undefined, done: true}
在這個生成器中,for...in循環用於枚舉集合併產生每一個屬性的值。結果與前一個示例徹底相同,但它大大縮短了。
迭代器的缺點是它們不適合表示異步數據源。ES2018的補救解決方案是異步迭代器和異步迭代。異步迭代器與傳統迭代器的不一樣之處在於,它不是以形式返回普通對象{value, done},而是返回知足的承諾{value, done}。異步iterable定義了一個返回異步迭代器的Symbol.asyncIterator方法(而不是Symbol.iterator)。
一個例子應該使這更清楚:
const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]() { const values = Object.keys(this); let i = 0; return { next: () => { return Promise.resolve({ value: this[values[i++]], done: i > values.length }); } }; } }; const iterator = collection[Symbol.asyncIterator](); console.log(iterator.next().then(result => { console.log(result); // → {value: 10, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: 20, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: 30, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: undefined, done: true} }));
請注意,不可能使用promises的迭代器來實現相同的結果。雖然普通的同步迭代器能夠異步肯定值,但它仍然須要同步肯定「完成」的狀態。
一樣,你可使用生成器函數簡化過程,以下所示:
const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]: async function * () { for (let key in this) { yield this[key]; } } }; const iterator = collection[Symbol.asyncIterator](); console.log(iterator.next().then(result => { console.log(result); // → {value: 10, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: 20, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: 30, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: undefined, done: true} }));
一般,生成器函數返回帶有next()方法的生成器對象。當next()被稱爲它返回一個{value, done}對,其value屬性決定產生價值。異步生成器執行相同的操做,只是它返回一個履行的promise {value, done}。
一個簡單的方法來遍歷一個迭代的對象是使用for...of的語句,但for...of不與異步iterables的工做value和done不一樣步肯定。所以,ES2018提供了for...await...of聲明。咱們來看一個例子:
const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]: async function * () { for (let key in this) { yield this[key]; } } }; (async function () { for await (const x of collection) { console.log(x); } })(); // logs: // → 10 // → 20 // → 30
在此代碼中,for...await...of語句隱式調用Symbol.asyncIterator集合對象上的方法以獲取異步迭代器。每次循環時,next()都會調用迭代器的方法,它返回一個promise。一旦解析了promise,就會value將結果對象的屬性讀取到x變量中。循環繼續,直到done返回的對象的屬性值爲true。
請記住,該for...await...of語句僅在異步生成器和異步函數中有效。違反此規則會致使a SyntaxError。
該next()方法能夠返回拒絕的承諾。要優雅地處理被拒絕的promise,您能夠將for...await...of語句包裝在語句中try...catch,以下所示:
const collection = { [Symbol.asyncIterator]() { return { next: () => { return Promise.reject(new Error('Something went wrong.')) } }; } }; (async function() { try { for await (const value of collection) {} } catch (error) { console.log('Caught: ' + error.message); } })(); // logs: // → Caught: Something went wrong.
Node.js的:
8.10.0(須要--harmony_async_iteration標誌)
10.0.0(全力支持)
Promise.prototype.finally
ES2018的另外一個使人興奮的補充是該finally()方法。之前有幾個JavaScript庫實現了相似的方法,這在許多狀況下證實是有用的。這鼓勵了Ecma技術委員會正式添加finally()到規範中。使用這種方法,程將可以執行一個代碼塊,而無論promise的命運如何。咱們來看一個簡單的例子:
fetch('https://www.google.com') .then((response) => { console.log(response.status); }) .catch((error) => { console.log(error); }) .finally(() => { document.querySelector('#spinner').style.display = 'none'; });
finally()不管操做是否成功,當您須要在操做完成後進行一些清理時,該方法會派上用場。在此代碼中,該finally()方法只是在獲取和處理數據後隱藏加載微調器。代碼不會複製then()和catch()方法中的最終邏輯,而是在履行或拒絕承諾時註冊要執行的函數。
能夠經過使用promise.then(func, func)而不是實現相同的結果promise.finally(func),可是必須在履行處理程序和拒絕處理程序中重複相同的代碼,或者爲它聲明一個變量:
fetch('https://www.google.com') .then((response) => { console.log(response.status); }) .catch((error) => { console.log(error); }) .then(final, final); function final() { document.querySelector('#spinner').style.display = 'none'; }
與then()和同樣catch(),該finally()方法老是返回一個promise,所以能夠連接更多方法。一般,將其finally()用做最後一個鏈,但在某些狀況下,例如在發出HTTP請求時,將另外一個連接catch()起來處理可能發生的錯誤是一種很好的作法finally()。
新的RegExp功能
ES2018爲RegExp對象增長了四個新功能,進一步提升了JavaScript的字符串處理能力。這些功能以下:
s(dotAll)標誌
命名捕獲組
Lookbehind斷言
Unicode屬性轉義
S(DOTALL)標誌
dot(.)是正則表達式模式中的特殊字符,它匹配除換行符以外的任何字符,例如換行符(n)或回車符(r)。匹配全部字符(包括換行符)的解決方法是使用具備兩個相反短字的字符類,例如[dD]。此字符類告訴正則表達式引擎找到一個數字(d)或非數字(D)的字符。所以,它匹配任何字符:
console.log(/one[\d\D]two/.test('one\ntwo')); // → true
ES2018引入了一種模式,其中點可用於實現相同的結果。可使用s標誌在每一個正則表達式的基礎上激活此模式:
console.log(/one.two/.test('one\ntwo')); // → false console.log(/one.two/s.test('one\ntwo')); // → true
使用標誌來選擇新行爲的好處是向後兼容性。所以,使用點字符的現有正則表達式模式不受影響。
命名捕獲組
在一些正則表達式模式中,使用數字來引用捕獲組可能會形成混淆。例如,使用/(d{4})-(d{2})-(d{2})/與日期匹配的正則表達式。因爲美式英語中的日期符號與英式英語不一樣,所以很難知道哪一個組指的是哪一天,哪一個組指的是月份:
const re = /(\d{4})-(\d{2})-(\d{2})/; const match= re.exec('2019-01-10'); console.log(match[0]); // → 2019-01-10 console.log(match[1]); // → 2019 console.log(match[2]); // → 01 console.log(match[3]); // → 10
ES2018引入了使用(?<name>...)語法的命名捕獲組。所以,匹配日期的模式能夠用不太模糊的方式編寫:
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const match = re.exec('2019-01-10'); console.log(match.groups); // → {year: "2019", month: "01", day: "10"} console.log(match.groups.year); // → 2019 console.log(match.groups.month); // → 01 console.log(match.groups.day); // → 10
可使用k<name>語法在模式中稍後調用命名的捕獲組。例如,要在句子中查找連續的重複單詞,可使用/b(?<dup>w+)s+k<dup>b/:
const re = /\b(?<dup>\w+)\s+\k<dup>\b/; const match = re.exec('Get that that cat off the table!'); console.log(match.index); // → 4 console.log(match[0]); // → that that
要將命名捕獲組插入到方法的替換字符串中replace(),您須要使用該$<name>構造。例如:
const str = 'red & blue'; console.log(str.replace(/(red) & (blue)/, '$2 & $1')); // → blue & red console.log(str.replace(/(?<red>red) & (?<blue>blue)/, '$<blue> & $<red>')); // → blue & red
後向斷言
ES2018爲JavaScript帶來了後瞻性斷言,這些斷言已在其餘正則表達式實現中提供多年。之前,JavaScript只支持超前斷言。lookbehind斷言用表示(?<=...),並可以根據模式以前的子字符串匹配模式。例如,若是要在不捕獲貨幣符號的狀況下以美圓,英鎊或歐元匹配產品的價格,您可使用/(?<=$|£|€)d+(.d*)?/:
const re = /(?<=\$|£|€)\d+(\.\d*)?/; console.log(re.exec('199')); // → null console.log(re.exec('$199')); // → ["199", undefined, index: 1, input: "$199", groups: undefined] console.log(re.exec('€50')); // → ["50", undefined, index: 1, input: "€50", groups: undefined]
還有一個負面版本的lookbehind,用表示(?<!...)。負外觀容許您僅在模式不在後面的模式以前匹配模式。例如,若是模式/(?<!un)available/沒有「un」前綴,則模式匹配可用單詞:
const re = /(?<!un)available/; console.log(re.exec('We regret this service is currently unavailable')); // → null console.log(re.exec('The service is available')); // → ["available", index: 15, input: "The service is available", groups: undefined]
Unicode的物業逃逸
ES2018提供了一種稱爲Unicode屬性轉義的新類型轉義序列,它在正則表達式中提供對完整Unicode的支持。假設要匹配字符串中的Unicode字符㉛。雖然㉛被認爲是一個數字,可是你不能將它與d速記字符類匹配,由於它只支持ASCII [0-9]字符。另外一方面,Unicode屬性轉義可用於匹配Unicode中的任何十進制數:
const str = '㉛'; console.log(/\d/u.test(str)); // → false console.log(/\p{Number}/u.test(str)); // → true
一樣,若是要匹配任何Unicode 字字母字符,你可使用p{Alphabetic}:
const str = 'ض'; console.log(/\p{Alphabetic}/u.test(str)); // → true // the \w shorthand cannot match ض console.log(/\w/u.test(str)); // → false
還有一個否認版本p{...},表示爲P{...}:
console.log(/\P{Number}/u.test('㉛')); // → false console.log(/\P{Number}/u.test('ض')); // → true console.log(/\P{Alphabetic}/u.test('㉛')); // → true console.log(/\P{Alphabetic}/u.test('ض')); // → false
除了字母和數字以外,還有幾個屬性能夠在Unicode屬性轉義中使用。
模板文字修訂
當模板文字緊跟在表達式以後時,它被稱爲標記模板文字。當您想要使用函數解析模板文字時,標記的模板會派上用場。請考慮如下示例:
function fn(string, substitute) { if(substitute === 'ES6') { substitute = 'ES2015' } return substitute + string[1]; } const version = 'ES6'; const result = fn`${version} was a major update`; console.log(result); // → ES2015 was a major update
在此代碼中,調用標記表達式(它是常規函數)並傳遞模板文字。該函數只是修改字符串的動態部分並返回它。
在ES2018以前,標記的模板文字具備與轉義序列相關的語法限制。後跟特定字符序列的反斜槓被視爲特殊字符:x解釋爲十六進制轉義符,u解釋爲unicode轉義符,後跟一個數字解釋爲八進制轉義符。其結果是,字符串,例如"C:xxxuuu"或者"ubuntu"被認爲是由解釋無效轉義序列,並會拋出SyntaxError。
ES2018從標記模板中刪除了這些限制,而不是拋出錯誤,表示無效的轉義序列爲undefined:
function fn(string, substitute) { console.log(substitute); // → escape sequences: console.log(string[1]); // → undefined } const str = 'escape sequences:'; const result = fn`${str} \ubuntu C:\xxx\uuu`;
請記住,在常規模板文字中使用非法轉義序列仍會致使錯誤:
const result = `\ubuntu`; // → SyntaxError: Invalid Unicode escape sequence
結束
ES2018中引入的幾個關鍵特性,包括異步迭代,休息/擴展屬性Promise.prototype.finally()以及RegExp對象的添加。雖然有些瀏覽器供應商還沒有徹底實現其中一些功能,但因爲像Babel這樣的JavaScript轉換器,它們今天仍然可使用。
ECMAScript正在迅速發展,而且每隔一段時間就會引入新功能,所以請查看完整提案列表,瞭解新功能的所有範圍。你有什麼特別興奮的新功能嗎?在評論中分享!