這周看了google IO上的一個(gè)視頻,講JS的新feature。還是挺有意思的,所以這次就做個(gè)“簡(jiǎn)報(bào)”,列一下值得一看的新feature。
Private Field
自從JS引進(jìn)class語(yǔ)法糖以來(lái),私有域的實(shí)現(xiàn)一直是個(gè)有爭(zhēng)議的話題。我記得以前的面試題還喜歡考《私有域有幾種實(shí)現(xiàn)方式?》——Symbol、WeakMap、Proxy等幾種“奇技淫巧”。今年TC39正式將#作為標(biāo)志符更新到Stage 3,雖然沒(méi)有官宣,但是V8已經(jīng)開始實(shí)驗(yàn)性地支持這個(gè)新特性了。
class Counter {
#counter = 0;
get value() {
return #counter;
}
}
const c = new Counter();
c.value // 0
c.#counter; // Syntax Error
如上,#counter就是Counter類的私有域,外部調(diào)用時(shí)會(huì)報(bào)出語(yǔ)法錯(cuò)誤。用慣了typescript的private,我看到#時(shí)還是覺(jué)得怪怪的。想嘗鮮的朋友,可以安裝最新版的Chrome或Node試驗(yàn)。相傳私有方法,私有g(shù)et和set很快也會(huì)被加入進(jìn)來(lái)。
Big Int
Big Int顧名思義大整數(shù),我們來(lái)做個(gè)計(jì)算題1234567890123456789 * 123,看看js的計(jì)算結(jié)果:151851850485185200000。有點(diǎn)長(zhǎng),不看別的就看末尾數(shù)0,顯然計(jì)算結(jié)果是錯(cuò)的。原因還是在JS數(shù)字精度丟失這一經(jīng)典問(wèn)題上,JS的安全精度在(-2^53, 2^53),超出了安全范圍就不準(zhǔn)了。
console.log(1234567890123456789 * 123) // 151851850485185200000
再看看新語(yǔ)法,末尾加個(gè)n,答案是151851850485185185047n,Bingo。
console.log(1234567890123456789n * 123n) // 151851850485185185047n
Array Flat & FlatMap
我個(gè)人是從java8才開始知道有flat的概念(嗯,就是這么后知);這個(gè)概念來(lái)自函數(shù)式編程,如果要講歷史典故得追溯到上個(gè)世紀(jì)中葉了。
-
新版JS中,
flat()被設(shè)計(jì)為高維數(shù)組一維化(降維打擊??)。var newArray = arr.flat(depth)- 參數(shù)depth: 指定要提取嵌套數(shù)組的結(jié)構(gòu)深度,默認(rèn)值為 1。
- 返回值: 一個(gè)包含將數(shù)組與子數(shù)組中所有元素的新數(shù)組。
const higherDimensionalArray = [ "a", ["b", "c"], ["d", ["e", "f"]]]; higherDimensionalArray.flat( 2 ); // [ "a", "b", "c", "d", "e", "f" ] -
flatMap和map很像,不同之處是flatMap會(huì)對(duì)回調(diào)結(jié)果一維化:const scattered = [ "my favorite", "fruit is", "red bayberry" ]; scattered.map( chunk => chunk.split(" ") ); // [["my", "favorite"], ["fruit", "is"], ["red", "bayberry"]] scattered.flatMap( chunk => chunk.split(" ") ); // ["my", "favorite", "fruit", "is", "red", "bayberry"]
Object.fromEntries
Object.fromEntries是Object.entries反向函數(shù):
let obj = {k1: 'v1', k2: 'v2', k3: 'v3'};
let entries = Object.entries(obj); // [['k1','v1'],['k2','v2'],['k3','v3']]
Object.fromEntries(entries); // {k1: 'v1', k2: 'v2', k3: 'v3'}
雖然互為反向,但是fromEntries比entries晚出了三四年,也挺奇怪的一件事。
Optional Catch Binding
允許開發(fā)者省略catch里的參數(shù)e,但好像也沒(méi)什么重大意義(汗)
try {
throw new Error('some error');
} catch {
console.log('no params for catch');
}
早期版本:
try {
throw new Error('some error');
} catch(e) {
console.log(e); // Error: "some error"
}
WeakRef
在WeakRef前,ES6中就有兩個(gè)Weak類了——WeakMap和WeakSet。我曾經(jīng)寫過(guò)一篇《ES6之WeakMap》,有興趣的朋友可以看一下。我們回顧一下JS垃圾回收機(jī)制,它主要通過(guò)“引用標(biāo)記”和“引用清除”兩個(gè)方法實(shí)現(xiàn)內(nèi)存回收。每當(dāng)對(duì)象多一次引用則“引用數(shù)”加1,少一次則減一;當(dāng)“引用數(shù)”為0時(shí),啟動(dòng)垃圾回收。以WeakMap為例,它存儲(chǔ)的對(duì)象都是弱引用,不會(huì)增加“引用數(shù)”,因此不會(huì)導(dǎo)致內(nèi)存溢出。看兩個(gè)例子:
//map.js
function memoryUsage() {
const used = process.memoryUsage().heapUsed;
console.log( Math.round(used / 1024 / 1024) + 'M' );
}
memoryUsage(); // ≈ 4M
let arr = new Array(1024 * 1024);
const map = new Map();
map.set(arr, 1);
global.gc();
memoryUsage(); // ≈ 12M
arr = null;
global.gc();
memoryUsage(); // ≈ 12M
//weakmap.js
memoryUsage(); // ≈ 4M
let arr = new Array(1024 * 1024);
const map = new WeakMap();
map.set(arr, 1);
global.gc();
memoryUsage(); // ≈ 12M
arr = null;
global.gc();
memoryUsage(); // ≈ 4M
分別執(zhí)行node --expose-gc map.js和node --expose-gc weakmap.js??梢院苊黠@地看到區(qū)別:在arr被置為null后,Map并沒(méi)有釋放Array,而WeakMap釋放了。原因正如上文所示:Map是強(qiáng)引用,arr清除后依舊保留了對(duì)new Array(1024 * 1024)的引用指向,而WeakMap并沒(méi)有保留,因此垃圾回收機(jī)制可以照常執(zhí)行。
再回到WeakRef。WeakRef也是不增加“引用數(shù)”的。我們來(lái)看看tc93上的介紹:
- WeakRef通過(guò)傳入Object直接構(gòu)造
new WeakRef({}) - 它有一個(gè)唯一的方法
deref返回構(gòu)造時(shí)傳入的對(duì)象;若對(duì)象已被回收,則返回undefined
舉個(gè)簡(jiǎn)單的例子,假如我們想對(duì)一個(gè)圖片(一般來(lái)說(shuō)是ArrayBuffer)做緩存,你很可能希望通過(guò)文件名去讀取該對(duì)象。直接使用Map很可能導(dǎo)致內(nèi)存溢出,但是WeakMap也不合適——它的key只能是object。在這個(gè)場(chǎng)景里WeakRef是很好的折衷手段,只需要“虛化”map的value值:我們既不需要在內(nèi)存中強(qiáng)引用巨大的ArrayBuffer,也可以使用string作為鍵值;當(dāng)ArrayBuffer被垃圾回收后,Map里只有一個(gè)很小的空WeakRef指向。如下:我們將getImage中獲得的ArrayBuffer虛引用后存入cache;正常情況下可以快速獲得image引用,當(dāng)外部作用域清除image的ArrayBuffer后,cache中就只能獲取一個(gè)undefined的WeakRef了,我們不用過(guò)多擔(dān)心內(nèi)存泄漏了。
function makeWeakCached(f) {
const cache = new Map();
return (key) => {
const ref = cache.get(key);
if (ref) {
const cached = ref.deref();
if (cached !== undefined) return cached;
}
const fresh = f(key);
cache.set(key, new WeakRef(fresh));
return fresh;
};
}
var getImageCached = makeWeakCached(getImage);
Promise
現(xiàn)代開發(fā)中,早已大量使用async/await語(yǔ)法糖了,很多新人可能并不是很了解Promise了。我們這種ES5過(guò)來(lái)的人,對(duì)Promise還是挺有印象的(被polyfill惡心過(guò)有數(shù)了)。尤其是Promise.all()和Promise.race()這種方法我現(xiàn)在還是經(jīng)常使用的。這次又新增了Promise.allSettled()和Promsie.any()。這兩個(gè)方法我很早就在《You don't know JS》里看到過(guò),概念可能已經(jīng)源遠(yuǎn)流長(zhǎng)了。具體功能如可以從函數(shù)名猜出大概:allSettled與all相對(duì),表明全部Promise執(zhí)行完后再返回,不似all只要有一個(gè)錯(cuò)誤直接reject;any和race相對(duì),表明只要有一個(gè)Promise fulfilled就返回then,只有全部reject才拋異常。
其他
再快速過(guò)一下其他幾個(gè)小更新
-
Array.Sort
以前V8實(shí)現(xiàn)數(shù)組排序是:10個(gè)元素以上是穩(wěn)定排序,10以下是不穩(wěn)定排序,現(xiàn)在改成全是穩(wěn)定排序了
-
String.trimStart() & String.trimEnd()
顧名思義,豐富了一下
trim()函數(shù)的使用場(chǎng)景 -
Function.toString()
Function也有toString方法了,可以打印函數(shù)源碼,不受高編影響
-
Symbol.description
竟然沒(méi)發(fā)現(xiàn)以前是無(wú)法打印Symbol value值的(汗)
var s = Symbol('Onion') console.log(s.description) // Onion -
Numeric Seperator
數(shù)字分隔符支持,挺常見的需求
let a = 1_000_000 let b = 1_019.42
其實(shí)我最想看到的是Optional Chaining,可惜還在Stage 1。
var street = user && user.address && user.address.street;
var oc_streat = user?.address?.stree;
小結(jié)
最后再推一波《What’s New in JavaScript》油管視頻。它還提到了其他很多有趣的新特性,有興趣的朋友點(diǎn)進(jìn)去看一下,英語(yǔ)也沒(méi)啥難度。
金三銀四過(guò)后,就是老同事們各奔東西了;當(dāng)年一起寫JS大前端的同僚,今天只剩我一人了。我倒不需要像管理層那樣精算人力,只是回想起自己這幾年工作經(jīng)歷——滄海桑田。眼看他起朱樓,眼看他宴賓客,眼看他。。。不可妄議了。人來(lái)人往,世間無(wú)不散之宴席,祝大家一切安好。