垃圾回收
JavaScript为开发者卸下了这个负担,通过自动内存管理实现内存分配和闲置资源回收。基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间(或者说在代码执行过程中某个预定的收集时间)就会自动运行。
-
JavaScript具有
自动垃圾收集机制
。 -
执行环境
会负责管理
代码执行过程中使用的内存
。 -
垃圾回收机制的原理:找出那些不再继续使用的内存,然后释放其占用的内存。垃圾收集器会按照固定的时间间隔,周期性地执行这一操作。
标记清理
JavaScript最常用的垃圾回收策略是标记清理(mark-and-sweep)。当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而在上下文中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要上下文中的代码在运行,就有可能用到它们。当变量离开上下文时,也会被加上离开上下文的标记。
-
垃圾回收程序运行
的时候,会标记内存中存储的所有变量
(记住,标记方法有很多种)。 -
然后,它会将
所有在上下文中的变量
,以及被在上下文中的变量引用的变量
的标记去掉。 -
在此之后
再被加上标记的变量
就是待删除的了,原因是任何在上下文中的变量
都访问不到它们了。 -
随后
垃圾回收程序做一次内存清理
,销毁带标记的所有值并收回它们的内存。
引用计数
另一种没那么常用的垃圾回收策略是引用计数(reference counting)。其思路是:对每个值都记录它被引用的次数。
-
另一种不太常见的垃圾收集策略叫做
引用计数
。引用计数的含义是跟踪记录每个值被引用的次数
。 -
声明变量并给它赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,那么引用数加1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减1。
-
当这个值的
引用次数变成0
时,则说明没有办法再访问这个值了,因此可以安全地收回其内存了。 -
当垃圾收集器下次再运行时,它就会
释放那些引用次数为0
的值所占用的内存。
性能
垃圾回收程序会周期性运行,如果内存中分配了很多变量,则可能造成性能损失,因此垃圾回收的时间调度很重要。
内存管理
将内存占用量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要,那么把它设置为null,从而释放其引用。这也可以叫作解除引用。这个建议最适合全局变量和全局对象的属性。
1.通过const和let声明提升性能
因为const和let都以块(而非函数)为作用域,所以相比于使用var,使用这两个新关键字可能会更早地让垃圾回收程序介入,尽早回收应该回收的内存。在块作用域比函数作用域更早终止的情况下,这就有可能发生。
2.隐藏类和删除操作
避免JavaScript的“先创建再补充”(ready-fire-aim)式的动态属性赋值,并在构造函数中一次性声明所有属性。
这样,两个实例基本上就一样了(不考虑hasOwnProperty的返回值),因此可以共享一个隐藏类,从而带来潜在的性能提升。不过要记住,使用delete关键字会导致生成相同的隐藏类片段。
在代码结束后,即使两个实例使用了同一个构造函数,它们也不再共享一个隐藏类。动态删除属性与动态添加属性导致的后果一样。最佳实践是把不想要的属性设置为null。这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。
3.内存泄漏
写得不好的JavaScript可能出现难以察觉且有害的内存泄漏问题。
-
意外声明全局变量是最常见但也最容易修复的内存泄漏问题。
-
使用JavaScript闭包很容易在不知不觉间造成内存泄漏。
4.静态分配与对象池
一个策略是使用对象池。在初始化的某一时刻,可以创建一个对象池,用来管理一组可回收的对象。应用程序可以向这个对象池请求一个对象、设置其属性、使用它,然后在操作完成后再把它还给对象池。由于没发生对象初始化,垃圾回收探测就不会发现有对象更替,因此垃圾回收程序就不会那么频繁地运行。
静态分配是优化的一种极端形式。如果你的应用程序被垃圾回收严重地拖了后腿,可以利用它提升性能。
参考文章
本文主要参考《JavaScrip高级程序设计》第三、四版