通过自动内存管理实现内存分配和闲置资源回收。基本思路:确定哪个变量不会再使用,然后释放它所占用的内存。这个过程是周期性
的,即垃圾回收程序每隔一定时间
就会自动运行
标记未使用的变量有不同的实现方式,常见的主要有标记清理
和引用计数
# 1. 标记清理
js最常用的垃圾回收策略是标记清理
。当变量进入上下文时,会被加上存在于上下文中的标记。当变量离开上下文时,也会被加上离开上下文的标记。标记过程不重要,关键是策略:垃圾回收程序运行的时候,最开始标记存储的所有变量
。**然后,它会将上下文中的变量,以及被这些变量引用的变量的标记去掉
。在此之后还剩下的标记的变量就是待删除的了。**因为任何在上下文中的变量都访问不到它们。虽然垃圾回收程序会做一次内存清理,销毁带标记的所有值并回收它们的内存。
# 2. 引用计数
另外一种没那么常用的垃圾回收策略是引用计数
。声明并赋值时给变量加上引用数为1,如果该变量又被其他其他变量引用或赋值,则引用数再加1。同理,如果对该变量引用的变量被其他值赋值过后,则该引用数减1。当一个值的引用变量为0时,就说明上下文没有访问这个变量了,则垃圾回收程序会释放引用数为0的值的内存。
缺点
:引用计数在循环引用时,会有严重问题。如果两个对象通过各自属性相互引用,那么它们的引用数永远不会变为0。如果函数被多次调用,则会导致大量的内存永远不会被释放。解决方法
是将变量设置为null
,会切断变量与其之前引用值的关系。下次垃圾回收程序运行时会被回收
# 性能
垃圾回收程序会周期性运行,如果内存中分配了很多变量,则可能造成性能损失,因此垃圾回收的时间调度很重要。
因此最好的办法是:无论什么时候开始收集垃圾,都能让它尽快结束工作。
在某些浏览器中是可以主动触发垃圾回收的,但不推荐。
# 内存管理
优化内存占用的最佳手段就是保证在执行代码时只保存必要的数据
。如果数据不再必要,那么把它设置为null,从而释放引用。这也可以叫做解除引用。这个方法最适合全局变量
和全局对象的属性
。因为局部变量在超出作用域后被自动解除引用。
注意:解除一个值的引用并不会自动导致相关内存被回收。而是为了确保该变量不在上下文中,下一次垃圾回收程序运行时回收它
- 通过 const 和 let 声明提升性能
在块作用域比函数作用域更早终止的情况下,const和let相比于使用var,使用这两个新关键字会更早地让垃圾回收程序介入,尽早回收应该回收的内存。
- 隐藏类和删除操作
V8 在将解释后的 JavaScript 代码编译为实际的机器码时会利用
隐藏类
。能够共享相同隐藏类的对象性能会更好。
function Article() {
this.title = 'Inauguration Ceremony Features Kazoo Band';
this.age = 18
}
let a1 = new Article();
let a2 = new Article();
两个实例共享相同的隐藏类(v8引擎才能看见),因为这两个实例共享同一个构造函数和原型
如果加了下面两种代码
// 构造函数中没有author属性
// 动态添加
a2.author = 'Jake';
// 动态删除
delete a2.age
此时两个实例就会对应两个不同的隐藏类。会带来潜在的性能提升。最佳实践是把不想要的属性设置为null
。这样就可以保持隐藏类不变和继续共享。同时也能达到删除引用值供垃圾回收程序回收的效果。
a1.author = null;
# 3. 内存泄漏
首先js的变量回收规则为:
- 全局变量不会被回收。
- 局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁。
- 只要被另外一个作用域所引用就不会被回收
意外声明全局变量
function setName() { name = 'lwl' }
解释器会把变量name当作
window
的属性来创建(相当于window.name = 'lwl'
)。 可想而知,在window对象上创建的属性,只要window本身不被清理就不会消失。这个问题很容易解决,只要在变量声明前头加上var
、let
或const
关键字即可定时器
let name = 'Jake'; setInterval(() => { console.log(name); }, 100);
定时器的回调通过闭包引用了外部变量,只要定时器一直运行,回调函数中引用的name就会一直占用内存。
闭包
let outer = function () { let name = 'Jake'; return function () { return name; }; };
调用
outer()
会导致分配给name
的内存被泄漏。以上代码执行后创建了一个内部闭包,只要返回的函数存在就不能清理name,因为闭包一直在引用着它。解决方法是:将不再使用的闭包实例手工删除。