JS是使用垃圾回收的语言,即:执行环境负责在代码执行时管理内存。
基本思路:
- 确定哪个变量不会再使用,然后释放它占用的内存。
- 这个过程是周期性的,即垃圾回收程序每隔一定时间(或者说代码执行过程中某个特定的时间)就会自动运行。
- 浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数
1.标记清理
JS最常用的垃圾回收策略
- 当变量进入上下文,如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。
- 当变量离开上下文时,会被加上一个离开上下文的标记
- 垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。
2.引用计数
没有那么常用。
思路是对每个值都记录它被引用的次数,以此判断是否需要清除
有一个严重的问题就是循环引用。即A引用B,B引用A。但是A和B是孤立存在的。没有被其他的引用。如果函数被多次调用,则会导致大量内存永远不会被释放
3.内存管理
在使用垃圾回收的编程环境中,开发者通常午需关心内存管理。不过JS运行在一个内存管理与垃圾回收都很特殊的环境。分配给浏览器的内存通常比分配给桌面软件的要少很多,分配给移动浏览器的就更少了。这更多出于安全考虑而不是别的,就是为了避免运行大量 JavaScript 的网页耗尽系统内存而导致操作系统崩溃。这个内存限制不仅影响变量分配,也影响调用栈以及能够同时在一个线程中执行的语句数量。
将内存占用量保持在一个较小的值可以让页面性能更好。优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据。如果数据不再必要,要么把它设置为null,从而释放其引用——解除引用:
- 解除引用最 适合全局变量和全局对象的属性。
- 局部变量在超出作用域后会被自动解除引用
- 注意:解除对一个值的引用并不会自动导致相关内存被回收。解除引用的关键在于确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收
(1)const和let声明变量来提升性能
ES6新增这两个关键字不仅有助于改善代码风格,而且同样有助于改进垃圾回收的过程。因为const和let都是块级作用域。使用这两个新关键字能更早地让垃圾回收程序介入,尽早回收应该回收地内存。在块级作用域比函数作用域更早终止地情况下,这就有可能发生。
(2)隐藏类和删除操作
根据JS所在地运行环境,有时候需要根据浏览器使用地JS引擎来采取不的性能优化策略。
Chrome使用V8引擎,V8将解释后的JS代码编译为实际的机器码时会利用“隐藏类”,这对代码性能非常重要:
原理暂时没有看懂,见JavaScript高级程序设计内存管理-隐藏类
总而言之,两条要求:
- 避免JS的先创建再补充式的动态属性赋值,并在构造函数中一次性声明所有属性
- 把不想要的属性设置为null。可以保持隐藏类不变和继续共享
(3)内存泄漏
写得不好的JS可能出现难以察觉且有害的内存泄露问题。在内存有限的设备上,或者在函数会被调用很多次的情况下,内存泄漏可能是个大问题,JavaScript 中的内存泄漏大部分是由不合理的引用导致的。
常见造成内存泄漏的原因:
- 意外声明全局变量:如没有使用关键字声明变量,解释器会把变量当作window的属性来创建
- 定时器的回调
- 闭包导致
(4)静态分配与对象池
说明:静态分配是优化的一种极端形式。如果你的应用程序被垃圾回收严重地拖了后腿,可以利用它提升性能。但这种情况并不多见。大多数情况,这都属于过早优化,因此不用考虑。