【Chrome web.dev Live 2020总结】第2天第3集:V8 / JavaScript的新特性
1. Optional Chaining :?
可选链操作符 ?. 可以按照操作符之前的属性是否有效,链式读取对象的属性或者使整个对象链返回 undefined。?. 运算符的作用与 . 运算符类似,不同之处在于,如果对象链上的引用是 nullish (null 或者 undefined),. 操作符会抛出一个错误,而 ?. 操作符则会按照短路计算的方式进行处理,返回 undefined。可选链操作符也可用于函数调用,如果操作符前的函数不存在,也将会返回 undefined。
当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链操作符也是很有帮助的。
官网文档:https://v8.dev/features/optional-chaining
2. Nullish Coalesing:??
空值合并操作符(??)是一个逻辑操作符,当左侧的表达式结果为 null 或者 undefined 时,其返回右侧表达式的结果,否则返回左侧表达式的结果。
与逻辑或操作符(||)不同,逻辑或操作符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 || 来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如,'' 或 0)时。见下面的例子。
官方文档:https://v8.dev/features/nullish-coalescing
3. parsing and streaming
自2015年随着ES6引入,浏览器的解析效率略微有点降低,但是随着近些年来的发展,浏览器的解析时间的影响慢慢降低。
最开始阶段,html加载到<script>标签引入的外部js时,首先会暂停html解析,先执行js的下载、解析、执行e,然后在继续parsing html。
但这样的效率还是很低的,于是js通过引入preloading,在html解析的main thread之外先完成下载,这样js的解析、执行阶段都将可以提前
既然,js可以实现提前下载,那么也可以通过在主线程之外异步的解析js,那么还可以进一步缩短浏览器的响应时间,于是<script>标签可以通过引入async属性,在html解析同时,异步的解析js
但是,这种异步操作并不是在所有时候都是完美的,因为技术原因,虽然可以实现将js解析从主线程移至全局线程池,但此时html解析和js之间的相互依赖可能会无法控制。
同步的js引入方式,在js解析过程,主线程html解析会有短时间停止,但是在停止这一段时间,html依旧可以完成部分交互逻辑,比如scroll、type、onClick其它函数等,这对处于loading状态的html来说,可以有效的提升html的loading时间。
既然,js的下载、解析已经移入了主线程之外的线程池,那也通过下载的同时解析js,这样可以进一步的缩短浏览器的loading时间
下图,展示了从2018年以来,通过parsing and streaming优化对parse performance的提升
4. memory
为了分析V8引擎中,内存换粗和垃圾回收机制。我们定义了一个MovingAvg类,并在MovingAvgComponent里面new了一个MovingAvg实例(模拟实现chat app,不定时执行如下工具类)
在实例完成后,通过设置this.movingAvg=null,将对象标记为不可达。因为在V8引擎中,会将不可达的对象是为可回收对象,从而实现触发js垃圾回收。从理论上说,这个方法可以完美的实现无效的内存回收,也不会造成额外的内存消耗。但是,随着chat app的不定时循环触发MovingAvgComponent,通过内存管理工具的结果来看,虽然每次gc触发都有一定的内存回收,但是总体趋势内存消耗越来越多。
因为MovingAvg类中,在listener时间中引用了this.events.push,这一操作导致整个MovingAvg在V8引擎中都被标记为可访问、可达的。所以,尽管在MovingAvgComponent类中,已经将this.movingAvg设为null,GC依旧将其识别为alive状态,不可回收。
让我们通过可达性图来明确这一点. 调用start() ,我们的对象图如下所示,其中实线箭头表示强烈引用. 通过MovingAvgComponent实例的实心箭头可访问的MovingAvgComponent均不可垃圾回收.
打完电话后stop()我们已经移除了强引用MovingAvgComponent实例的MovingAvg实例,但不是通过套接字的监听器.
因此,只要不删除事件侦听器, MovingAvg实例中的侦听器通过引用this可使整个实例保持活动状态.
到目前为止,解决方案是通过dispose方法手动注销事件侦听器.
这种方法的缺点是它是手动内存管理,通过监听器实现垃圾回收,这种方式会消耗大量的计算性能。此时可以考虑使用WeakRefs方式实现内存回收。
5. WeakRefs
WeakRef通过创建一个弱引用到实际的事件侦听器,然后包装是解决困境WeakRef在外部事件侦听器. 这样,垃圾收集器可以清理实际的事件侦听器及其保留的活动内存,例如MovingAvg实例及其events数组.
WeakRef通过在 addWeakListener 中,创建一个WeakRef事件监听,在wrapper内部,通过deref其deref。 由于事件侦听器包装在WeakRef ,因此对该事件的唯一强引用是MovingAvg实例上的listener属性. 也就是说,我们已经成功地将事件侦听器的生存期与MovingAvg实例的生存期MovingAvg .
返回可达性图,使用WeakRef实现调用start()之后,我们的对象图如下WeakRef`,其中虚线箭头表示弱引用.
调用stop() ,我们删除了对监听器的唯一强引用:
最终,在发生垃圾回收之后,将收集MovingAvg实例和侦听器:
但是这里仍然存在一个问题:我们通过将WeakRef包装为listener,从而为listener添加了一个间接级别,但由于listener最初泄漏的原因, addWeakListener的包装仍在泄漏. 当然,这是一个较小的泄漏,因为只有包装器而不是整个MovingAvg实例在泄漏,但这仍然是泄漏。使用新的FinalizationRegistry API,我们可以注册一个回调,以在垃圾回收器关闭注册对象时运行。这种回调称为终结器 。可以向FinalizationRegistry注册一个回调,以在内部事件侦听器被垃圾回收时从套接字移除wrapper。最终实现如下所示:
此时,垃圾回收效率如下:
官方文档: https://v8.dev/features/weak-references
6. point compression
当前chrome已经从32-bit,增加为64-bit,在提升chrome性能的同事,也提升了内存消耗,因为指针从4个字节变成了8个字节。所以,如果chrome在同时打开数以千计的image、视频、游戏时,可能会耗尽内存。
可以通过分割64-bit指针成两块,这样可以压缩实现4个字节的指针,具体实现可以详见: https://v8.dev/blog/pointer-compression。
通过指针压缩,可以有效的降低chrome的内存使用,通过测试对比几个真实的网站,可以发现通过指针压缩可以降低约40%的内存使用。
官方文档:https://v8.dev/blog/pointer-compression
7. v8 lite
v8引擎的精简模式,这是一个比较有趣的话题,如果我们放弃performance,只关注与提升memory,会实现怎样的效果呢?因为,在某些内存不充足的设备上,V8引擎无法得到自己需要的内存。
所以,如果分析一下V8引擎如何使用内存、以及在V8中不通对象对于内存占用的比例,可以考虑降低一些对象的内存配比。当然,这么做完全会影响用户体验和浏览器响应,但是降低不等于无,虽然降低了体验但是却可以确保功能的正常进行。
image-20200707113601814
所以,精简模式可以通过禁用功能优化来进一步节省内存,当将每个优化的影响分开时,很明显,不同的页面会从这些优化中获得不同的收益比例. 展望未来,我们将继续确定潜在的优化方案,这些优化方案可以进一步减少V8的内存使用量,同时仍然保持JavaScript执行的极快速度.
官方文档: https://v8.dev/blog/v8-lite
- 点赞
- 收藏
- 关注作者
评论(0)