前几天 Google IO 上 V8 团队为咱们分享了《What's New in JavaScript》主题,分享的语速很慢推荐你们能够都去听听就当锻炼下听力了。看完以后我整理了一个文字版帮助你们快速了解分享内容,嘉宾主要是分享了如下几点:javascript
开场就用 11x faster
数字把你们惊到了,也有不少同窗好奇究竟是怎么作到的。其实这个优化并非最近作的,去年11月的时候 V8 团队就发了一篇文章 《Faster async functions and promises》,这里面就很是详尽的讲述了如何让 async/await 优化到这个速度的,其主要归功于如下三点:html
2008年 Chrome 出世,10年 Chrome 引入了 Crankshaft 编译器,多年后的今天这员老将已经没法知足现有的优化需求,毕竟当时的做者也不曾料想到前端的世界会发展的这么快。关于为什么使用 TurboFan 替换掉 Crankshaft,你们能够看看《Launching Ignition and TurboFan》,原文中是这么说的:前端
Crankshaft 仅支持优化 JavaScript 的一部分特性。它并无经过结构化的异常处理来设计代码,即代码块并无经过try、catch、finally等关键字划分。另外因为为每个新的特性Cranksshaft都将要作九套不一样的框架代码适应不一样的平台,所以在适配新的Javascript语言特性也很困难。还有Crankshaft框架代码的设计也限制优化机器码的扩展。尽管V8引擎团队为每一套芯片架构维护超过一万行代码,Crankshaft也不过为Javascript挤出一点点性能。 via:《Javascript是如何工做的:V8引擎的内核Ignition和TurboFan》java
而 TurboFan 则提供了更好的架构,可以在不修改架构的状况下添加新的优化特性,这为面向将来优化 JavaScript 语言特性提供了很好的架构支持,能让团队花费更少的时间在作处理不一样平台的特性和编码上。从原文的数据对比中就能够看到,仅仅是换了个编译器优化就在 8 倍左右了…… 给 V8 的大佬们跪下了。node
而 Orinoco 新的 GC 引擎则是使用单独线程异步去处理,让其不影响 JS 主线程的执行。至于最后说的 async/await 的 BUG 则是让 V8 团队引起思考,将 async/await 本来基于 3 个 Promise 的实现减小成 2 个,最终减小成 1 个!最后达到了写 async/await 比直接写 Promise 还要快的结果。git
咱们知道 await
后面跟的是 Promise 对象,可是即便不是 Promise JS 也会帮咱们将其包装成 Promise。而在 V8 引擎中,为了实现这个包装,至少须要一个 Promise,两个微任务过程。这个在自己已是 Promise 的状况下就有点划不来了。而为了实现 async/await 在 await 结束以后要从新原函数的上下文并提供 await
以后的结果,规范定义此时还须要一个 Promise,这个在 V8 团队看来是没有必要的,因此他们建议规范去除这个特性。github
最后的最后,官方还建议咱们:多使用 async/await 而不是手写 Promise 代码,多使用 JavaScript 引擎提供的 Promise 而不是本身去实现。segmentfault
随着 Babel 的出现,JS 的语法糖简直不要太多,Numeric Seperator 算是一个。简单的说来它为咱们手写数字的时候提供给了分隔符的支持,让咱们在写大数字的时候具备可读性。数组
实际上是个很简单的语法糖,为何我会单独列出来讲呢,主要是由于它正好解决了我以前一个实现的痛点。我有一个需求是一堆文章数据,我要按照产品给的规则去插入广告。如图非红框是文章,红框处是广告。因为插入规则会根据产品的需(心)求(情)频繁变化,因此咱们最开始使用了两个变量进行标记:promise
const news = [1, 3, 5, 6, 7, 9, 10, 11];
const ads = [2, 4, 8, 12];
复制代码
当位置发生变化的时候咱们就须要同时对两个变量进行修改,这样致使了维护上的成本。因此我想了一个办法,广告的位置标记为 1,文章的位置标记为 0,使用纯二进制的形式来表示个记录,这样子就变成了:
+---+---+---+
| 0 | 1 | 0 |
+---+---+---+
| 1 | 0 | 0 |
+---+---+---+
| 0 | 1 | 0 |
+---+---+---+
| 0 | 0 | 1 |
+---+---+---+
1 011 010 100 010 001
// 首位为常量 1
// 2-4 位记录一行多少条
// 后续按照新闻和广告的位置进行记录
复制代码
最后咱们使用一个变量 0b1011010100010001
就完成了两种信息的记录。这样作将不少数据集成在了一块儿解决了咱们以前的问题,可是它带来了新的问题,你们也能够看到注释中按照空格劈开的话你们看的还比较明白,可是在段头将空格去除以后在阅读程度上就形成了很是大的困难了。而数字分隔符这个语法糖正好就能解决这个问题,0b1_011_010_100_010_001
这样阅读起来就好不少了。
虽然在大部分的场景 async/await 均可以了,可是很差意思 Promise 有些场景仍是不可替代的。Promsie.all()
和 Promise.race()
就是这种特别的存在。而 Promise.allSettled()
和 Promise.any()
则是新增长的方法, 相较于它们的前辈,这俩拥有忽略错误达到目的的特性。
咱们以前有一个需求,是须要将文件安全的存储在一个存储服务中,为了灾备咱们其实有两个 S3,一个 HBase 还有本地存储。因此每次都须要诸如如下的逻辑:
for(const service of services) {
const result = await service.upload(file);
if(result) break;
}
复制代码
但其实我并不关心错误,个人目的是只要保证有一个服务最终能执行成功便可,因此 Promise.any()
其实就能够解决这个问题。
await Promise.any( services.map(service => service.upload(file)) );
复制代码
Promise.allsettled()
和 Promise.any()
的引入丰富了 Promise 更多的可能性。说不定之后还会增长更多的特性,例如 Promise.try()
, Promise.some()
, Promise.reduce()
...
WeakRef 这个新类型我最开始是不太理解的,毕竟我总感受 Chrome 已经长大了,确定会本身处理垃圾了。然而事情并无我想的那么简单,咱们知道 JS 的垃圾回收主要有“标记清除”和“引用计数”两种方法。引用计数是只要变量多一次引用则计数加 1,少一次引用则计数减 1,当引用为 0 时则表示你已经没有利用价值了,去垃圾站吧!
在 WeakRef 以前其实已经有两个相似的类型存在了,那就是 WeakMap 和 WeakSet。以 WeakMap 为例,它规定了它的 Key 必须是对象,并且它的对象都是弱引用的。举个例子:
//map.js
function usageSize() {
const used = process.memoryUsage().heapUsed;
return Math.round(used / 1024 / 1024 * 100) / 100 + 'M';
}
global.gc();
console.log(usageSize()); // ≈ 3.23M
let arr = new Array(5 * 1024 * 1024);
const map = new Map();
map.set(arr, 1);
global.gc();
console.log(usageSize()); // ≈ 43.22M
arr = null;
global.gc();
console.log(usageSize()); // ≈ 43.23M
复制代码
//weakmap.js
function usageSize() {
const used = process.memoryUsage().heapUsed;
return Math.round(used / 1024 / 1024 * 100) / 100 + 'M';
}
global.gc();
console.log(usageSize()); // ≈ 3.23M
let arr = new Array(5 * 1024 * 1024);
const map = new WeakMap();
map.set(arr, 1);
global.gc();
console.log(usageSize()); // ≈ 43.22M
arr = null;
global.gc();
console.log(usageSize()); // ≈ 3.23M
复制代码
分别执行 node --expose-gc map.js
和 node --expose-gc weakmap.js
就能够发现区别了。在 arr 和 Map 中都保留了数组的强引用,因此在 Map 中简单的清除 arr 变量内存并无获得释放,由于 Map 还存在引用计数。而在 WeakMap 中,它的键是弱引用,不计入引用计数中,因此当 arr 被清除以后,数组会由于引用计数为0而被回收掉。
正如分享中所说,WeakMap 和 WeakSet 足够好,可是它要求键必须是对象,在某些场景上不太试用。因此他们暴露了更方便的 WeakRef 类型。在 Python 中也存在 WeakRef 类型,干的事情比较相似。其实咱们主要注意 WeakRef 的引用是不计引用计数的,就好理解了。例如 MDN 中所说的引用计数没办法清理的循环引用问题:
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "azerty";
}
f();
复制代码
若是试用 WeakRef 来改写,因为 WeakRef 不计算引用计数,因此计数一直为 0,在下一次回收中就会被正常回收了。
function f() {
var o = new WeakRef({});
var o2 = o;
o.a = o2;
return "azerty";
}
f();
复制代码
在以前的一个多进程需求中,咱们须要将子进程中的数据发送到主进程中,咱们使用的方式是这样写的:
const metric = 'event';
global.DATA[metric] = {};
process.on(metric, () => {
const data = global.DATA[metric];
delete global.DATA[metric];
return data;
});
复制代码
代码就看着比较怪,因为 global.DATA[metric]
做为强引用,若是直接在事件中 return global.DATA[metric]
的话,因为存在引用计数,那么这个全局变量是一直占用内存的。此时若是使用 WeakRef 改写一下就能够减小 delete
的逻辑了。
const metric = 'event';
global.DATA[metric] = new WeakRef({});
process.on(metric, () => {
const ref = global.DATA[metric];
if(ref !== undefined) {
return ref.deref();
}
return ref;
});
复制代码
除了我上面讲的几个特性以外,还有不少其余的特性也很是一颗赛艇。例如 String.matchAll()
让咱们作屡次匹配的时候不再用写 while 了!Intl 本地化类的支持,让咱们能够早日抛弃 moment.js,特别是 RelativeTimeFormat
类真是解放了咱们的生产力,不过目前接口的配置彷佛比较定制化,不知道后续的细粒度需求支持状况会如何。
参考资料: