精讀《What's new in javascript》
1. 引言
本週精讀的內容是:Google I/O 19。
2019 年 Google I/O 介紹了一些激動人心的 JS 新特性,這些特性有些已經被主流瀏覽器實現,並支援 polyfill,有些還在草案階段。
我們可以看到 JS 語言正變得越來越嚴謹,不同規範間也逐漸完成了閉環,而且在不斷吸納其他語言的優秀特性,比如 WeakRef,讓 JS 在成為使用範圍最廣程式語言的同時,也越成為程式語言的集大成者,讓我們有信心繼續跟隨 JS 生態,不用被新生的小語種分散精力。
2. 精讀
本視訊共介紹了 16 個新特性。
private class fields
私有成員修飾符,用於 Class:
class IncreasingCounter { #count = 0; get value() { return this.#count; } increment() { this.#count++; } }
通過 #
修飾的成員變數或成員函式就成為了私有變數,如果試圖在 Class 外部訪問,則會丟擲異常:
const counter = new IncreasingCounter()
counter.#count
// -> SyntaxError
counter.#count = 42
// -> SyntaxError
雖然 #
這個關鍵字被吐槽了很多次,但結論已經塵埃落定了,只是個語法形式而已,不用太糾結。
目前僅 Chrome、Nodejs 支援。
Regex matchAll
正則匹配支援了 matchAll
API,可以更方便進行正則遞迴了:
const string = 'Magic hex number: DEADBEEF CAFE' const regex = /\b\p{ASCII_Hex_Digit}+\b/gu/ for (const match of string.matchAll(regex)) { console.log(match) } // Output: // ['DEADBEEF', index: 19, input: 'Magic hex number: DEADBEEF CAFE'] // ['CAFE', index: 28, input: 'Magic hex number: DEADBEEF CAFE']
相比以前在 while
語句裡迴圈正則匹配,這個 API 真的是相當的便利。And more,還順帶提到了 Named Capture Groups
,這個在之前的 精讀《正則 ES2018》 中也有提到,具體可以點過去閱讀,也可以配合 matchAll
一起使用。
Numeric literals
大數字面量的支援,比如:
1234567890123456789 * 123;
// -> 151851850485185200000
這樣計算結果是丟失精度的,但只要在數字末尾加上 n
,就可以正確計算大數了:
1234567890123456789n * 123n; // -> 151851850485185185047n
目前 BigInt 已經被 Chrome、Firefox、Nodejs 支援。
BigInt formatting
為了方便閱讀,大數還支援了國際化,可以適配成不同國家的語言表達形式:
const nf = new Intl.NumberFormat("fr");
nf.format(12345678901234567890n);
// -> '12 345 678 901 234 567 890'
記住 Intl
這個內建變數,後面還有不少國際化用途。
同時,為了方便程式設計師閱讀程式碼,大數還支援帶下劃線的書寫方式:
const nf = new Intl.NumberFormat("fr");
nf.format(12345678901234567890n);
// -> '12 345 678 901 234 567 890'
目前已經被 Chrome、Firefox、Nodejs 支援。
flat & flatmap
等價於 lodash flatten 功能:
const array = [1, [2, [3]]];
array.flat();
// -> [1, 2, [3]]
還支援自定義深度,如果支援 Infinity
無限層級:
const array = [1, [2, [3]]];
array.flat(Infinity);
// -> [1, 2, 3]
這樣我們就可以配合 .map
使用:
[2, 3, 4].map(duplicate).flat();
因為這個用法太常見,js 內建了 flatMap
函式代替 map
,與上面的效果是等價的:
[2, 3, 4].flatMap(duplicate);
目前已經被 Chrome、Firefox、Safari、Nodejs 支援。
fromEntries
fromEntries
是 Object.fromEntries
的語法,用來將物件轉化為陣列的描述:
const object = { x: 42, y: 50, abc: 9001 };
const entries = Object.entries(object);
// -> [['x', 42], ['y', 50]]
這樣就可以對物件的 key 與 value 進行加工處理,並通過 fromEntries
API 重新轉回物件:
const object = { x: 42, y: 50, abc: 9001 }
const result Object.fromEntries(
Object.entries(object)
.filter(([ key, value]) => key.length === 1)
.map(([ key, value ]) => [ key, value * 2])
)
// -> { x: 84, y: 100 }
不僅如此,還可以將 object 快速轉化為 Map:
const map = new Map(Object.entries(object));
目前已經被 Chrome、Firefox、Safari、Nodejs 支援。
Map to Object conversion
fromEntries
建立了 object 與 map 之間的橋樑,我們還可以將 Map 快速轉化為 object:
const objectCopy = Object.fromEntries(map);
目前已經被 Chrome、Firefox、Safari、Nodejs 支援。
globalThis
業務程式碼一般不需要訪問全域性的 window 變數,但是框架與庫一般需要,比如 polyfill。
訪問全域性的 this 一般會做四個相容,因為 js 在不同執行環境下,全域性 this 的變數名都不一樣:
const getGlobalThis = () => {
if (typeof self !== "undefined") return self; // web worker 環境
if (typeof window !== "undefined") return window; // web 環境
if (typeof global !== "undefined") return global; // node 環境
if (typeof this !== "undefined") return this; // 獨立 js shells 指令碼環境
throw new Error("Unable to locate global object");
};
因此整治一下規範也合情合理:
globalThis; // 在任何環境,它就是全域性的 this
目前已經被 Chrome、Firefox、Safari、Nodejs 支援。
Stable sort
就是穩定排序結果的功能,比如下面的陣列:
const doggos = [
{ name: "Abby", rating: 12 },
{ name: "Bandit", rating: 13 },
{ name: "Choco", rating: 14 },
{ name: "Daisy", rating: 12 },
{ name: "Elmo", rating: 12 },
{ name: "Falco", rating: 13 },
{ name: "Ghost", rating: 14 }
];
doggos.sort((a, b) => b.rating - a.rating);
最終排序結果可能如下:
[
{ name: "Choco", rating: 14 },
{ name: "Ghost", rating: 14 },
{ name: "Bandit", rating: 13 },
{ name: "Falco", rating: 13 },
{ name: "Abby", rating: 12 },
{ name: "Daisy", rating: 12 },
{ name: "Elmo", rating: 12 }
];
也可能如下:
[
{ name: "Ghost", rating: 14 },
{ name: "Choco", rating: 14 },
{ name: "Bandit", rating: 13 },
{ name: "Falco", rating: 13 },
{ name: "Abby", rating: 12 },
{ name: "Daisy", rating: 12 },
{ name: "Elmo", rating: 12 }
];
注意 choco
與 Ghost
的位置可能會顛倒,這是因為 JS 引擎可能只關注 sort
函式的排序,而在順序相同時,不會保持原有的排序規則。現在通過 Stable sort 規範,可以確保這個排序結果是穩定的。
目前已經被 Chrome、Firefox、Safari、Nodejs 支援。
Intl.RelativeTimeFormat
Intl.RelativeTimeFormat
可以對時間進行語義化翻譯:
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
rtf.format(-1, "day");
// -> 'yesterday'
rtf.format(0, "day");
// -> 'today'
rtf.format(1, "day");
// -> 'tomorrow'
rtf.format(-1, "week");
// -> 'last week'
rtf.format(0, "week");
// -> 'this week'
rtf.format(1, "week");
// -> 'next week'
不同語言體系下,format
會返回不同的結果,通過控制 RelativeTimeFormat
的第一個引數 en
決定,比如可以切換為 ta-in
。
Intl.ListFormat
ListFormat
以列表的形式格式化陣列:
const lfEnglish = new Intl.ListFormat("en");
lfEnglish.format(["Ada", "Grace"]);
// -> 'Ada and Grace'
可以通過第二個引數指定連線型別:
const lfEnglish = new Intl.ListFormat("en", { type: "disjunction" });
lfEnglish.format(["Ada", "Grace"]);
// -> 'Ada or Grace'
目前已經被 Chrome、Nodejs 支援。
Intl.DateTimeFormat -> formatRange
DateTimeFormat
可以定製日期格式化輸出:
const start = new Date(startTimestamp);
// -> 'May 7, 2019'
const end = new Date(endTimestamp);
// -> 'May 9, 2019'
const fmt = new Intl.DateTimeFormat("en", {
year: "numeric",
month: "long",
day: "numeric"
});
const output = `${fmt.format(start)} - ${fmt.format(end)}`;
// -> 'May 7, 2019 - May 9, 2019'
最後一句,也可以通過 formatRange
函式代替:
const output = fmt.formatRange(start, end);
// -> 'May 7 - 9, 2019'
目前已經被 Chrome 支援。
Intl.Locale
定義國際化本地化的相關資訊:
const locale = new Intl.Locale("es-419-u-hc-h12", {
calendar: "gregory"
});
locale.language;
// -> 'es'
locale.calendar;
// -> 'gregory'
locale.hourCycle;
// -> 'h12'
locale.region;
// -> '419'
locale.toString();
// -> 'es-419-u-ca-gregory-hc-h12'
目前已經被 Chrome、Nodejs 支援。
Top-Level await
支援在根節點生效 await
,比如:
const result = await doSomethingAsync();
doSomethingElse();
目前還沒有支援。
Promise.allSettled/Promise.any
Promise.allSettled
類似 Promise.all
、Promise.any
類似 Promise.race
,區別是,在 Promise reject 時,allSettled
不會 reject,而是也當作 fulfilled 的訊號。
舉例來說:
const promises = [
fetch("/api-call-1"),
fetch("/api-call-2"),
fetch("/api-call-3")
];
await Promise.allSettled(promises);
即便某個 fetch
失敗了,也不會導致 reject
的發生,這樣在不在乎是否有專案失敗,只要拿到都結束的訊號的場景很有用。
對於 Promise.any
則稍有不同:
const promises = [
fetch("/api-call-1"),
fetch("/api-call-2"),
fetch("/api-call-3")
];
try {
const first = await Promise.any(promises);
// Any of ths promises was fulfilled.
console.log(first);
} catch (error) {
// All of the promises were rejected.
}
只要有子項 fulfilled,就會完成 Promise.any
,哪怕第一個 Promise reject 了,而第二個 Promise fulfilled 了,Promise.any
也會 fulfilled,而對於 Promise.race
,這種場景會直接 rejected。
如果所有子項都 rejected,那 Promise.any
也只好 rejected 啦。
目前已經被 Chrome、Firefox 支援。
WeakRef
WeakRef 是從 OC 抄過來的弱引用概念。
為了解決這個問題:當物件被引用後,由於引用的存在,導致物件無法被 GC。
所以如果建立了弱引用,那麼物件就不會因為存在的這段引用關係而影響 GC 了!
具體用法是:
const obj = {};
const weakObj = new WeakRef(obj);
使用 weakObj
與 obj
沒有任何區別,唯一不同時,obj
可能隨時被 GC,而一旦被 GC,弱引用拿到的物件可能就變成 undefined
,所以要做好錯誤保護。
3. 總結
JS 這幾個特性提升了 JS 語言的成熟性、完整性,而且看到其訪問控制能力、規範性、國際化等能力有著重加強,解決的都是 JS 最普遍遇到的痛點問題。
那麼,這些 JS 特性中,你最喜歡哪一條呢?想吐槽哪一條呢?歡迎留言。
討論地址是:精讀《What's new in javascript》 · Issue #159 · dt-fe/weekly
如果你想參與討論,請 點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公眾號
special Sponsors
- DevOps 全流程平臺
版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證)