Javascript会找出已经用不上的变量,将其释放,也就意味着该变量的声明周期结束了。
但是Javascript有两种变量,即全局变量和局部变量,全局变量却不会被释放,直至页面关闭。
局部变量整个声明周期,在函数内声明开始,为其分配相应的堆或栈空间进行存储,直至函数执行结束,变量也就不再使用,其占用的空间也将被释放。
上述情况,有个特殊情况会导致局部函数变量不会因为函数执行结束而被回收,就是局部变量被外部所调用(如闭包),函数虽然执行结束,但函数外部变量依然指向了该局部变量,仍然被使用,不能回收。
举个例子:
function func1() {
const makeit = 'https://blog.makeit.vip';
}
function func2() {
const makeit = 'https://mall.makeit.vip';
return makeit;
}
const m1 = func1();
const m2 = func2();
// 上述例子中,分别定义了 func1 与 func2 两个函数
// func1 执行时为 makeit 变量分配了空间,执行完成后即释放了
// func2 执行时,同样也为 makeit 分配了空间,但 makeit 最后被返回后赋值给了 m2
// func2 执行完后,makeit 依然被使用中,导致 makeit 占用的空间不会被释放
1.标记清除
Javascript中常见的一种垃圾回收方式
当变量进入执行环境时,则该变量被标记为“进入环境”的状态,原则上来讲,“进入环境”的状态是永远不会被清除的,因为进入相应的执行环境后,该变量就随时可能被调用到。当离开执行环境时,则被标记为“离开环境”的状态
垃圾收集器会给存储在内存中的所有变量都加上标记,紧接着会过滤出执行环境中的变量及在被环境中的变量所引用的变量(闭包),去掉这些变量后,剩下的就是待回收的变量了,因为环境中已经无法再访问这些变量了,垃圾回收器则会回收这些变量值,回收这些变量值所占的内存空间。
2.引用计数
引用计数的策略就是跟踪记录每个值的使用次数
当生命一个变量并将一个引用类型的值赋值给该变量,则该值的引用次数 + 1
如果该值的结果变为另外一个,则该值的引用次数 - 1
当这个值的引用次数为 0 的时候,说明已经无法访问该变量了,则垃圾回收器会在运行时回收那些引用次数为 0 的值所占的空间
但是这种策略,有个循环引用的问题,容易引起内存泄漏,具体如下示例
function makeit() {
let m1 = {};
let m2 = {};
m1.vip = m2;
m2.miit = m1;
}
// 上述例子中,m1 与 m2 的引用次数都为 2,在 [ 标记清除 ] 的策略中,没问题,离开环境后即回收了
// 但在 [ 引用计数 ] 中就出现问题了,引用次数始终不为 0,则意味着垃圾回收器始终都不会回收,这也就造成了内存泄漏
// 再举个例子:
// 该段代码,乍一看,没什么问题,但是 elem.onclick 方法会引用外部环境中的变量,这自然也就包括了 elem
// 如果没有最后的 elem = null 手动回收的话,就造成了内存泄漏
window.onload = function makeit() {
const elem = document.getElementById('makeit');
elem.onclick = function() {
// ...
}
// 手动回收
elem = null;
}
3.触发时间
什么时候触发垃圾回收器进行垃圾回收呢?
IE6有个回收触发点,就是变量达到 256 个,对象 4096 个,或 64KB 的字符,满足三种情况中的任何一种,都将触发垃圾回收,但这又有个问题,如果当前脚本本身就需要这么多的变量或对象,不就导致垃圾回收器一直在不间断的工作状态中了?
来,针对刚刚那个问题,IE7做了调整,触发条件不固定,可以动态修改,默认初始值与IE6一致。