js 有自动垃圾回收机制,会定期对那些我们不再使用的变量、对象所占用的内存进行释放。JS 的回收机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但这个过程不是时时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
内存管理
内存管理包括:
- 内存分配:申明变量、函数、对象,系统会自动分配内存
- 内存使用:读写内存,使用变量、函数等
- 内存回收:使用完毕后,由垃圾回收机制自动回收不再使用的内存
内存分配
JS 内存空间的类型:
- 堆:存放复杂对象
- 栈:存放常见的基础类型和函数,存在于栈中的数据大小和生存期是确定的,可以明确知道每个区块的大小,因此栈的速度快于堆(后进先出)
- 池:一般归为栈中,存放常量
内存回收
JS 中存在两种变量 ——— 全局变量和局部变量,全局变量会从执行函数开始,到函数执行结束。局部变量在函数中,从执行函数开始,到函数执行结束,这些局部变量所占用的空间会被释放。
还有一种情况就是,局部变量不会随着函数的结束而被回收,那就是局部变量被函数外部的变量使用(比如必包)
标记清除(常用)
当变量进入执行环境时,标记为“进入环境”,当变量离开执行环境时,则标记“离开环境”。标记为“离开环境”的变量则可以被回收。
引用计数
统计引用类型变量声明后被引用的次数,当次数为0时,该变量被回收。
但引用计数有一个明显的缺点是 —— 循环引用
JS V8 引擎的垃圾回收机制(分代回收)
将保存对象的堆进行了分代:
- 对象最初被分在 新生代,当指针达到新生区的末尾,会有一次垃圾回收清理(小周期),清理掉新生代中不再活跃的死对象
- 对于超过 2 个小周期的对象,则需要将其移动到 老生区 。
内存泄漏
虽然 JS 会自动回收垃圾,但是如果我们的代码写法不当,会让代码一直处于 “进入环境” 的状态,无法被回收
意外的全局变量
function test() {
hh = 1 // 创建了全局变量hh
this.variable = 2 // this 指向全局对象window
}
被遗忘的计时器或回调函数
var someRes = getData();
// 如果id为Node的元素从 DOM 中移除,该定时器仍会存在,最外层的 someRes 也不会被释放
setInterval(function() {
var node = document.getElementById('Node')
if (node) {
node.innerHTML = JSON.stringify(someRes)
}
}, 1000)
闭包
闭包可以维持函数内局部变量,使其得不到释放。
// 解决办法1:将事件处理函数定义在外面
function bindEvent() {
var obj = document.createElement('xxx')
obj.onclick = onclickHandler
}
// 解决办法2:在定义事件处理函数的外部函数中,删除对 dom 的引用
function bindEvent() {
var obj = document.createElement('xxx')
obj.onclick = function() {...}
obj = null
}
内存泄漏的识别方法
步骤:
- 打开控制台,切换到 performance
- 勾选 Screenshots 和 memory
- 左上角小圆点开始录制
- 停止录制
如果垃圾回收之后的最低值min,在不断上涨,那么肯定有较严重的内存泄漏问题。
避免内存泄漏的方法
- 减少不必要的全局变量
- 避免死循环
- 避免创建过多的对象
- 对象尽量复用
- 在循环中的函数表达式,能复用最好放在循环外面
好题分享:
Q1: 什么是垃圾?
没有被引用的变量就是垃圾
Q2:如果回收垃圾
使用标记清除法