學習在於總結,發現並沒有對於新出的一些語言特性進行總結,正好最近有時間,可以把這些進行總結以及運用,也許在項目中已經使用。

ES7

  • Array includes方法
  • 求冪運算符

ES8

  • Async Functions
  • SharedArrayBuffer和Atomics
  • Object.values 和 Object.entries
  • String padding
  • Object.getOwnPropertyDescriptors()
  • 函數參數列表和調用中的尾逗號

ES9

  • 對象的Rest以及Spread
  • Asynchronous iteration
  • Promise.prototype.finally()
  • 正則擴展--先行斷言以及後行斷言
  • 正則擴展--Unicode轉義
  • 正則擴展--命名捕獲組

ES10

  • Array的flat()方法和flatMap()方法
  • String的trimStart()方法和trimEnd()方法
  • Object.fromEntries()
  • 未完待續...

1. Array includes 方法

includes() 方法用來判斷數組中是否包含一個特定的值,如果包含 ,返回true,若不包含,返回false。

此方法對標 數組 的 indexOf(),產生主要是為了解決indexOf 什麼問題呢?

  • 簡便的判斷

includes() 方法返回值是布爾值可以直接在if判斷,而indexOf返回的是值類型 -1,判斷時候更加冗餘。

const ary = [0];
if (ary.indexOf(0) !== -1) {
console.log("存在")
}
if (ary.includes(1)) {
console.log("存在")
}

  • 對於 NaN 的判斷

indexOf 無法對NaN進行準確的判斷,includes 可以對於進行正確判斷。

const ary = [NaN];
console.log(ary.indexOf(NaN)) //-1
console.log(ary.includes(NaN))//true

  • 對於 underfind 的判斷

當數組值為空,indexOf 判斷空為 -1,includes則會進行正確的判斷。

const ary = new Array(3);
console.log(ary.indexOf(undefined)); //-1
console.log(ary.includes(undefined)) //true

2. 求冪運算符(**)

很簡單的語法糖 Math.pow的替代品,Math.pow(2,3) 等同於 2 ** 3。

3. Async Functions

谷歌中默認啟用非同步函數,我們可以利用 async/await 像編寫同步代碼一樣的編寫基於Promise的代碼,當您使用await某個Promise 時,函數暫停執行,直到Promise執行返回結果,這種暫停不會阻塞主線程,如果Promise執行,則返回值,如果拒絕,則返回錯誤值。

async/await 會提高代碼的可讀行,去掉層層回調。

function logFetch(url) {
return fetch(url)
.then(response => response.text())
.then(text => {
console.log(text);
}).catch(err => {
console.error(fetch failed, err);
});
}

使用async/await改寫:

async function logFetch(url) {
try {
const response = await fetch(url);
console.log(await response.text());
}
catch (err) {
console.log(fetch failed, err);
}
}

使用async/await 構建 api模塊:

4. SharedArrayBuffer

在了解SharedArrayBuffer之前我們首先要了解一下內存之類的知識。

假如我們要將一個數字放進內存中,我們會首先將它轉化為32位或者64位字大小,如果要放入一個非數字,JS Engine會通過編碼器運行該值,然後通過編碼方式,例如UTF-8,獲取該值得二進位表示。JS引擎會在內存中找到可以存放此二進位的位置,進行分配內存,之後js引擎會持續跟蹤該變數是否仍可從程序中的任何位置訪問。如果無法再訪問該變數,則將回收內存,以便JS引擎可以在其中放置新值。如果無法在訪問到它們,則進行清除,此過程稱為垃圾回收。JS類語言稱為內存管理語言,它並不會直接操作內存,自動管理內存會使管理人員更加輕鬆,但是會產生一定的性能開銷。

但是,例如C等手動管理內存語言,C沒有JavaScript在內存上做的那個抽象層。相反,你直接在內存上運行。您可以從內存載入內容,並可以將內容存儲到內存中。假如React使用C寫出來,那麼它可以藉助WebAssembly來進行內存管理,關於什麼是WebAssembly?

WebAssembly是一種新的編碼方式,可以在現代的網路瀏覽器中運行 - 它是一種低級的類彙編語言,具有緊湊的二進位格式,可以接近原生的性能運行,並為諸如C / C ++等語言提供一個編譯目標,以便它們可以在Web上運行。它也被設計為可以與JavaScript共存,允許兩者一起工作。對於網路平台而言,WebAssembly具有巨大的意義——它提供了一條途徑,以使得以各種語言編寫的代碼都可以以接近原生的速度在Web中運行。在這種情況下,以前無法以此方式運行的客戶端軟體都將可以運行在Web中。

WebAssembly被設計為可以和JavaScript一起協同工作——通過使用WebAssembly的JavaScript API,你可以把WebAssembly模塊載入到一個JavaScript應用中並且在兩者之間共享功能。這允許你在同一個應用中利用WebAssembly的性能和威力以及JavaScript的表達力和靈活性,即使你可能並不知道如何編寫WebAssembly代碼。

那麼,我們為什麼需要ArrayBuffers?

ArrayBuffer 對象用來表示通用的、固定長度的原始二進位數據緩衝區。ArrayBuffer 不能直接操作,而是要通過類型數組對象或DataView對象來操作,它們會將緩衝區中的數據表示為特定的格式,並通過這些格式來讀寫緩衝區的內容。

即使你在JS中也可以通過ArrayBuffer對內存數據進行處理,你為什麼需要進行處理?正如前面所說的自動內存管理語言對於內存處理都有一種權衡,增加一些開銷,某種程度會導致性能問題。JS對於新創建的變數,因為引擎會對其進行分析,所以可能需要要為它進行預留大約2倍以上內存空間,這可能導致內存大量的浪費。

var buffer = new ArrayBuffer(8);

console.log(buffer.byteLength);
// expected output: 8

除了使用ArrayBuffer時,您不能將任何JavaScript類型放入其中,如對象或字元串。您可以添加的唯一內容是位元組,實際上並沒有將這個位元組直接添加到ArrayBuffer中。就其本身而言,這個ArrayBuffer不知道該位元組應該有多大,或者不應該將不同類型的數字轉換為位元組。ArrayBuffer本身只是一堆零和一行。ArrayBuffer不知道該數組中第一個元素和第二個元素之間的除法位置。

為什麼我們需要SharedArrayBuffer?

ArrayBuffers可以減少主線程必須完成的工作量。更加高效的做法是分開進行工作,在其他語言中使用線程來進行分解工作,在JS中使用Web Worker,但是他們不像其他語言一樣,它們是不共享內存的,也就是你干你的,我干我的。如果我們想要共享某些數據就必須進行複製,通過函數postMessage來進行。postMessage接受放入的其他對象,對其進行序列化,將其發送給其他Web工作者,然後將其反序列化並放入內存中。對於某些類型的數據,如ArrayBuffers,您可以執行所謂的傳輸內存。這意味著移動特定的內存塊,以便其他Web工作者可以訪問它。但是第一個Web工作者再也無法訪問它了。

使用SharedArrayBuffer,兩個Web工作者(兩個線程)都可以寫入數據並從同一塊內存中讀取數據。這意味著他們沒有使用postMessage的通信開銷和延遲。兩個Web工作人員都可以立即訪問數據。但是,同時從兩個線程立即訪問存在一些危險。它可以導致所謂的競爭條件。

5. Object.values 和 Object.entries

這兩個api就比較簡單了。

Object.values()方法返回一個給定對象自身的所有可枚舉屬性值的數組,值的順序與使用for...in循環的順序相同 ( 區別在於 for-in 循環枚舉原型鏈中的屬性 )。

var obj = { foo: bar, baz: 42 };
console.log(Object.values(obj)); // [bar, 42]

// array like object
var obj = { 0: a, 1: b, 2: c };
console.log(Object.values(obj)); // [a, b, c]

Object.entries()方法返回一個給定對象自身可枚舉屬性的鍵值對數組,其排列與使用for...in 循環遍歷該對象時返回的順序一致(區別在於 for-in 循環也枚舉原型鏈中的屬性)。

const obj = { foo: bar, baz: 42 };
console.log(Object.entries(obj)); // [ [foo, bar], [baz, 42] ]

// array like object
const obj = { 0: a, 1: b, 2: c };
console.log(Object.entries(obj)); // [ [0, a], [1, b], [2, c] ]

6. String padding

padStart()方法用另一個字元串填充當前字元串(重複,如果需要的話),以便產生的字元串達到給定的長度。填充從當前字元串的開始(左側)應用的。

padEnd() 方法會用一個字元串填充當前字元串(如果需要的話則重複填充),返回填充後達到指定長度的字元串。從當前字元串的末尾(右側)開始填充。

abc.padStart(10); // " abc"
abc.padStart(10, "foo"); // "foofoofabc"
abc.padStart(6,"123465"); // "123abc"
abc.padStart(8, "0"); // "00000abc"
abc.padStart(1); // "abc"

abc.padEnd(10); // "abc "
abc.padEnd(10, "foo"); // "abcfoofoof"
abc.padEnd(6, "123456"); // "abc123"
abc.padEnd(1); // "abc"

7. 結尾逗號

自我認為此新特性唯一好處就是代碼結構更加明了,易讀。當你需要再次增加屬性時候,方便許多。。。。。

let obj = {
first: Cat,
last: Dog,
};
let arr = [
red,
green,
blue,
];

8. 對象的 Rest /Spread 屬性

在ES6數組中,我們引入了 Rest /Spread屬性。

// Rest
const numbers = [1, 2, 3, 4, 5]
[first, second, ...others] = numbers

// Spread
const numbers = [1, 2, 3, 4, 5]
const sum = (a, b, c, d, e) => a + b + c + d + e
const sum = sum(...numbers)

在ES9中可以在對象中使用此屬性。

// Rest
const { first, second, ...others } = { first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }
first // 1
second // 2
others // { third: 3, fourth: 4, fifth: 5 }
// Spread
const items = { first, second, ...others }
items //{ first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }

9. Asynchronous iteration (非同步迭代)

新的for-await-of構造允許您使用非同步可迭代對象作為循環迭代:

for await (const o of array) {
console.log(o)
}

10. Promise.prototype.finally()

finally()允許您運行一些代碼,無論 promise 的執行成功或失敗。

fetch(xxx)
.then(console.log(成功))
.catch(error => console.error(error))
.finally(() => console.log(結束))

11. 正則擴展--先行斷言以及後行斷言

先行斷言(lookahead):您可以使用 ?= 匹配一個字元串,該字元串後面跟著一個特定的子字元串,?!執行逆操作,匹配一個字元串,該字元串後面沒有一個特定的子字元串。

/a(?= b)/.test(a c b) //false
/a(?= b)/.test(a b c) //true

/a(?! b)/.test(a c b) // true
/a(?! b)/.test(a b c) // false

上面例子表示的是 a 的後面是 b 採用的是先行斷言。

先行斷言(lookahead)使用?=符號。

/(?<=a) b/.test(c b) //false
/(?<=a) b/.test(a b) // true

/(?<!a) b/.test(c b) //true
/(?<!a) b/.test(a b) // false

上面例子表示的是 b 的前面是 a 採用的是後行斷言。

12. 正則擴展--命名捕獲組

在 ES2018 中,可以為捕獲組分配一個名稱,而不是僅在結果數組中分配一個 slot(插槽):

const re = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/
const result = re.exec(2019-05-30)

// result.groups.year === 2019;
// result.groups.month === 05;
// result.groups.day === 30;

13. 正則擴展--Unicode轉義

Unicode 標準為每個符號分配各種屬性和屬性值,比如希臘字母π在 Unicode 中有獨特的屬性和屬性值,在ES9之前,我們是無法直接匹配這種獨特的屬性,只能藉助一些正則表達式的庫,例如 xregexp 這種正則表達式的庫,來創建增強型的正則表達式。

const regexGreekSymbol = XRegExp(\p{Greek}, A)
regexGreekSymbol.test(π) // true

這種庫是運行時的依賴,不利於性能要求較高的web應用,並且 當Unicode 標準更新時,必須要更新 xregexp 才能使用新數據,至於π 為什麼是Greek,需要參看 Unicode編碼表 。

在新功能中使用 p{} 匹配所有 Unicode 字元,否定為 P{}

任何 unicode 字元都有一組屬性。 例如,Script確定語言系列,ASCII是一個布爾值, 對於 ASCII 字元,值為true,依此類推。 您可以將此屬性放在花括弧中,正則表達式將檢查是否為真。

/^p{ASCII}+$/u.test(eee) // true
/^p{ASCII}+$/u.test(EEE1@) // true
/^p{ASCII}+$/u.test(WWW??) // false

我們可以在Unicode字元資料庫找到所有對應的屬性,例如還有ASCII_Hex_Digit,Uppercase,Lowercase,White_Space,Alphabetic,Emoji 等,ASCII_Hex_Digit是另一個布爾屬性,用於檢查字元串是否僅包含有效的十六進位數字。以及可以檢查語言例如希臘語以及印度語。

/^p{ASCII_Hex_Digit}+$/u.test(555ABCDEF) // true
/^p{ASCII_Hex_Digit}+$/u.test(h) // false

/^p{Lowercase}$/u.test(a) // true
/^p{Uppercase}$/u.test(A) // true

/^p{Emoji}+$/u.test(B) // false
/^p{Emoji}+$/u.test(????) // true

^p{Script=Greek}+$/u.test(η?) // true
/^p{Script=Latin}+$/u.test(hey) // true

優點:

  • 不需要運行時依賴
  • 正則表達式不需要使用 Unicode 區間來判斷特點的內容
  • Unicode 屬性轉義自動保持最新,每當 Unicode 標準更新時,ECMAScript 引擎更新其數據即可

14. Array的 flat()方法和flatMap()方法.

flat() 方法會按照一個可指定的深度遞歸遍曆數組,並將所有元素與遍歷到的子數組中的元素合併為一個新數組返回。flat()方法主要作用有兩個方面:

  • 扁平化數組,並且可以指定扁平化的層級depth
  • 移除數組中的空項

ar arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

//使用 Infinity 作為深度,展開任意深度的嵌套數組
arr3.flat(Infinity);
// [1, 2, 3, 4, 5, 6]

// 移除空項
var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]

這裡涉及到一個前端面試題,如何扁平化數組?在沒有api支持之前,我們是這樣做的:

var arr1 = [1, 2, [3, 4]];
arr1.flat();
方法一
// 反嵌套一層數組
arr1.reduce((acc, val) => acc.concat(val), []);// [1, 2, 3, 4]

方法二:
// 或使用 ...
const flatSingle = arr => [].concat(...arr);

方法三遞歸
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
}
else {
result.push(arr[i])
}
}
return result;
}
方法四toString()
function flatten(arr) {
return arr.toString().split(,).map(function(item){
return +item
})
}

flatMap()

flatMap() 方法首先使用映射函數映射每個元素,然後將結果壓縮成一個新數組。它與map 和 深度值1的 flat 幾乎相同,但 flatMap通常在合併成一種方法的效率稍微高一些。 返回一個新的數組,其中每個元素都是回調函數的結果,並且結構深度depth值為1。

注意:只會將 flatMap 中的函數返回的數組 「壓平」 一層

var arr1 = [1, 2, 3, 4];

arr1.map(x => [x * 2]);
// [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]

// 只會將 flatMap 中的函數返回的數組 「壓平」 一層
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]

let arr = ["今天天氣不錯", "", "早上好"]
arr.map(s => s.split(""))
// [["今", "天", "天", "氣", "不", "錯"],[""],["早", "上", "好"]]
arr.flatMap(s => s.split());
// ["今", "天", "天", "氣", "不", "錯", "", "早", "上", "好"]

15. String的trimStart()方法和trimEnd()方法

String.trimStart()可用於從字元串的開頭去掉空白,trimLeft()是此方法的別名。注意:trimStart()方法返回的是一個新的字元串,並不改變原來的字元串。並且新字元串的長度會改變。

var text = Hello world! ;

console.log(text); //" Hello world! "
console.log(text.trimStart()); // "Hello world! "
console.log(text) //" Hello world! "

// length
var str = foo ;

console.log(str.length); // 8
str = str.trimStart();
console.log(str.length); // 5
console.log(str); // foo

String.trimEnd()可用於從字元串的尾部去掉空白。trimRight()是此方法的別名。注意:返回一個新字元串,表示從其(右)端剝去空白的調用字元串。同樣,新字元串長度會改變。

var text = Hello world! ;

console.log(text); // " Hello world! "
console.log(text.trimEnd()); // " Hello world!"

var str = foo ;

console.log(str.length); // 8
str = str.trimEnd();
console.log(str.length); // 6
console.log(str); // foo

16. Object.fromEntries()

Object.fromEntries()創建一個對象或將鍵值對轉換為一個對象。注意:它只接受Iterable迭代 例如:Object.fromEntries(Iterable)。 返回值是一個新對象,其屬性由iterable的條目給出。

// 情況一:將Map為Object
let entries = new Map([["name", "deep"], ["age", 26]]);
console.log(Object.fromEntries(entries));
// { name: deep, age: 26 }

// 情況二:將一個轉換Array為一個Object
const arr = [ [0, a], [1, b], [2, c] ];
const obj = Object.fromEntries(arr);
console.log(obj); // { 0: "a", 1: "b", 2: "c" }

// 情況三: 使用Object.fromEntries其反向方法Object.entries()和數組操作方法
const object1 = { a: 1, b: 2, c: 3 };

const object2 = Object.fromEntries(
Object.entries(object1)
.map(([ key, val ]) => [ key, val * 2 ])
);
console.log(object2);
// { a: 2, b: 4, c: 6 }

推薦閱讀:

相关文章