Javascript通过自动内存管理实现内存分配和闲置资源回收,主要用到以下两种策略
- 标记清理
- 引用计数
这个是Js最常用的垃圾回收策略,举个例子,当变量进入上下文,比如在函数内部声明一个变量,这个变量会被加上存在于上下文的标记,当变量离开上下文时也会被加上离开上下文的标记(实际实现有差异,这里只是表示那个意思)
- 标记方式:特殊位的反转、维护一个列
- 原理:垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后去掉在环境中的变量的标记,然后剩下的被标记的变量就被视为准备删除的变量
- 目前主流浏览器都是使用标记清除式的垃圾回收策略,只不过收集的间隔有所不同
IE等老版本浏览器采用的方式,声明变量并赋值引用数就标记为1,如果该值又被赋值给其他变量,引用数加1,以此类推,如果被赋值的变量被其他变量覆盖,引用数减一,当一个值的引用数是0,就可以回收了
- 存在的问题:循环引用问题 定义两个对象变量,并通过属性相互引用,引用数为2
- 解决方法:主动切断,直接设置为null,下次回收时就会被回收
虽然JS有垃圾回收的机制,但毕竟在浏览器中,可以分配到的内存其实很有限,为了避免大量Js的网页耗尽内存,将内存用量保持在比较小的范围还是非常重要的,根据上面所描述的标记清除离开作用域环境就会自动回收,全局变量内存占用最好手动解除(设置为null),局部变量在超出作用域后会自动解除
- 通过const和let声明提升性能:主要是其块级作用域能让回收机制尽可能早的回收变量内存
- V8引擎的隐藏类和对象的delete删除操作:V8会将创建的对象实例与隐藏类关联起来,能够共享相同隐藏类的对象性能会更好,因为他们共享的是同一个构造函数和原型,比如下面的代码
function Article () {
this.title = '高级程序设计'
}
let a = new Article()
let b = new Article()
// V8会在后台配置,让这两个类的实例共享相同的隐藏类Article
如果之后添加了这段代码:
b.author = 'worm'
此时的两个Article实例就会对应两个隐藏类,根据这个隐藏类的大小和这种操作的频率,这有可能会对性能产生明显的影响,解决的方式就是尽量避免先创建再补充
形式的动态属性赋值,在构造函数中一次声明所有属性
function Article () {
this.title = '高级程序设计'
this.author = 'worm'
}
let a = new Article()
let b = new Article()
另一种会影响隐藏类的的是delete
操作,即使共用构造函数,也不再共享隐藏类,如:
delete b.author // a,b不再共享隐藏类
b.author = null // 解决方法是需要删除的话直接赋值为null,这样的话就共享隐藏类
- 内存泄漏
内存泄漏,大部分是由于不合理引用造成的,如
- 意外声明的全局变量,此时的name作为window的变量,页面不关闭就不会被清除
function foo () {
name = 'worm'
}
- 定时器悄悄导致内存泄漏,如;
let name = 'worm'
setInterval(() => {
console.log(name)
}, 1000)
只要定时器一直运行,他对name的引用就一直存在,垃圾回收就不会回收 3. 闭包导致的内存泄漏
let outer = function () {
let name = 'worm'
return function () {
return name
}
}
调用outer()
就会导致分配给name的内存泄漏,只要返回的函数存在就不能回收name,如果这些变量很大,问题就更明显
- 静态分配与对象池
其实就是想办法避免浏览器的过度回收,避免垃圾回收机制的频繁触发,因为频繁的垃圾回收也是很消耗性能的,开发者无法直接控制什么时候可以进行垃圾回收但是可以间接控制触发垃圾回收的条件