《What’s new in Javascript》简报

这周看了google IO上的一个视频,讲JS的新feature。还是挺有意思的,所以这次就做个“简报”,列一下值得一看的新feature。

Private Field

自从JS引进class语法糖以来,私有域的实现一直是个有争议的话题。我记得以前的面试题还喜欢考《私有域有几种实现方式?》——Symbol、WeakMap、Proxy等几种“奇技淫巧”。今年TC39正式将#作为标志符更新到Stage 3,虽然没有官宣,但是V8已经开始实验性地支持这个新特性了。

class Counter {
    #counter = 0;
    get value() {
        return #counter;
    }
}

const c = new Counter();

c.value // 0
c.#counter; // Syntax Error

如上,#counter就是Counter类的私有域,外部调用时会报出语法错误。用惯了typescript的private,我看到#时还是觉得怪怪的。想尝鲜的朋友,可以安装最新版的Chrome或Node试验。相传私有方法,私有get和set很快也会被加入进来。

Big Int

Big Int顾名思义大整数,我们来做个计算题1234567890123456789 * 123,看看js的计算结果:151851850485185200000。有点长,不看别的就看末尾数0,显然计算结果是错的。原因还是在JS数字精度丢失这一经典问题上,JS的安全精度在(-2^53, 2^53),超出了安全范围就不准了。

console.log(1234567890123456789 * 123) // 151851850485185200000

再看看新语法,末尾加个n,答案是151851850485185185047n,Bingo。

console.log(1234567890123456789n * 123n) // 151851850485185185047n

Array Flat & FlatMap

我个人是从java8才开始知道有flat的概念(嗯,就是这么后知);这个概念来自函数式编程,如果要讲历史典故得追溯到上个世纪中叶了。

  • 新版JS中,flat()被设计为高维数组一维化(降维打击??)。

    var newArray = arr.flat(depth)
    
    • 参数depth: 指定要提取嵌套数组的结构深度,默认值为 1。
    • 返回值: 一个包含将数组与子数组中所有元素的新数组。
    const higherDimensionalArray = [ "a", ["b", "c"], ["d", ["e", "f"]]];
    higherDimensionalArray.flat( 2 );  // [ "a", "b", "c", "d", "e", "f" ]
    
  • flatMapmap很像,不同之处是flatMap会对回调结果一维化:

    
    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反向函数:


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'}

虽然互为反向,但是fromEntriesentries晚出了三四年,也挺奇怪的一件事。

Optional Catch Binding

允许开发者省略catch里的参数e,但好像也没什么重大意义(汗)

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中就有两个Weak类了——WeakMap和WeakSet。我曾经写过一篇《ES6之WeakMap》,有兴趣的朋友可以看一下。我们回顾一下JS垃圾回收机制,它主要通过“引用标记”和“引用清除”两个方法实现内存回收。每当对象多一次引用则“引用数”加1,少一次则减一;当“引用数”为0时,启动垃圾回收。以WeakMap为例,它存储的对象都是弱引用,不会增加“引用数”,因此不会导致内存溢出。看两个例子:

//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

分别执行node --expose-gc map.jsnode --expose-gc weakmap.js。可以很明显地看到区别:在arr被置为null后,Map并没有释放Array,而WeakMap释放了。原因正如上文所示:Map是强引用,arr清除后依旧保留了对new Array(1024 * 1024)的引用指向,而WeakMap并没有保留,因此垃圾回收机制可以照常执行。

再回到WeakRef。WeakRef也是不增加“引用数”的。我们来看看tc93上的介绍:

  • WeakRef通过传入Object直接构造new WeakRef({})
  • 它有一个唯一的方法deref返回构造时传入的对象;若对象已被回收,则返回undefined

举个简单的例子,假如我们想对一个图片(一般来说是ArrayBuffer)做缓存,你很可能希望通过文件名去读取该对象。直接使用Map很可能导致内存溢出,但是WeakMap也不合适——它的key只能是object。在这个场景里WeakRef是很好的折衷手段,只需要“虚化”map的value值:我们既不需要在内存中强引用巨大的ArrayBuffer,也可以使用string作为键值;当ArrayBuffer被垃圾回收后,Map里只有一个很小的空WeakRef指向。如下:我们将getImage中获得的ArrayBuffer虚引用后存入cache;正常情况下可以快速获得image引用,当外部作用域清除image的ArrayBuffer后,cache中就只能获取一个undefined的WeakRef了,我们不用过多担心内存泄漏了。

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

现代开发中,早已大量使用async/await语法糖了,很多新人可能并不是很了解Promise了。我们这种ES5过来的人,对Promise还是挺有印象的(被polyfill恶心过有数了)。尤其是Promise.all()Promise.race()这种方法我现在还是经常使用的。这次又新增了Promise.allSettled()Promsie.any()。这两个方法我很早就在《You don't know JS》里看到过,概念可能已经源远流长了。具体功能如可以从函数名猜出大概:allSettled与all相对,表明全部Promise执行完后再返回,不似all只要有一个错误直接reject;any和race相对,表明只要有一个Promise fulfilled就返回then,只有全部reject才抛异常。

其他

再快速过一下其他几个小更新

  • Array.Sort

    以前V8实现数组排序是:10个元素以上是稳定排序,10以下是不稳定排序,现在改成全是稳定排序了

  • String.trimStart() & String.trimEnd()

    顾名思义,丰富了一下trim()函数的使用场景

  • Function.toString()

    Function也有toString方法了,可以打印函数源码,不受高编影响

  • Symbol.description

    竟然没发现以前是无法打印Symbol value值的(汗)

    var s = Symbol('Onion')
    console.log(s.description) // Onion
    
  • Numeric Seperator

    数字分隔符支持,挺常见的需求

    let a = 1_000_000
    let b = 1_019.42
    

其实我最想看到的是Optional Chaining,可惜还在Stage 1。

var street = user && user.address && user.address.street;

var oc_streat = user?.address?.stree;

小结

最后再推一波《What’s New in JavaScript》油管视频。它还提到了其他很多有趣的新特性,有兴趣的朋友点进去看一下,英语也没啥难度。

金三银四过后,就是老同事们各奔东西了;当年一起写JS大前端的同僚,今天只剩我一人了。我倒不需要像管理层那样精算人力,只是回想起自己这几年工作经历——沧海桑田。眼看他起朱楼,眼看他宴宾客,眼看他。。。不可妄议了。人来人往,世间无不散之宴席,祝大家一切安好。

相关博客

  • 《ES6之WeakMap》
  • Javascript垃圾回收机制

你可能感兴趣的:(《What’s new in Javascript》简报)