总结:在JS中,我们直到函数执行会形成私有的上下文,进行进栈执行。当执行完毕后,会有一个出栈释放的过程。正常情况下,当出栈时,上下文的内容不会被外部上下文所引用,就会将这个上下文进行消除。但是我们每次创建引用类型,无论是Object、function,都会开辟一个堆内存,浏览器会在默认空闲的时候对这些内存进行释放,以减少浏览器压力。这样的释放,我们叫做垃圾回收。当然,也存在一样无法释放的问题,这样就会导致内存泄漏。
垃圾回收
垃圾回收在不同的浏览器中,使用的方式是不同的。主要区分谷歌和IE
谷歌浏览器
- 使用引用查找 进行回收。开辟的堆内存,在浏览器自己默认为空闲时刻,就会查找所有的内存引用,会将那些不被引用的内存进行释放。进而给浏览器减压。
IE浏览器
- 使用计数器机制 进行回收。如果创建的内存被引用一次,那么会计数1;再被引用,计数为2;以此类推。当移除时候,每移除一次,则计数减一。直到被引用降为 0.那么浏览器会被内存释放。
内存泄漏的情况
- 正常来说,为了降低压力,浏览器会将不被引用,或者计数次数变为0的内存进行释放。但是当遇到内存不能被释放的情况,场景多了后,就会造成 “内存泄漏”
1. 循环引用导致内存泄漏
-
当两个或多个对象之间存在相互引用,发生循环引用,导致内存无法释放
function fn() { let obj1 = {}, obj2 = {}; obj1.o = obj2; obj2.o = obj1; return obj1 } let F = fn(); -
上面可以看到,fn返回了 obj1,同时obj1 与 obj2 循环调用。F=obj1. 上级上下文引用 fn,导致fn无法释放。
2. 定时器
-
在真实项目中,我们会用到定时器Interval or Timeout 。比如倒计时等情况。但是有时候没有清除这些定时器,一样会一直占用空间
let count = 1; function countFn() { count++; setTimeout(countFn, 1000); log(count); } countFn(); -
这个例子是 Timeout 一直未清除,会一直在占用内存 countFn[AAAFFF000]
3. 全局变量没有清除
-
在项目中,如果定义了全局变量,那么只有等这个页面关闭,项目结束,该变量才会销毁,否则会一直存在
var gloabV = "data"
4. 闭包使用
-
我们在使用函数的时候,经常会对某些信息进行保护和保存操作,但是在保存的过程中,可能会造成内存无法释放的情况
function fn() { let x = function() {}; return x; } let obj = fn(); -
上面代码中,obj 一直在引用 AAAFFF000中的 x。导致这个堆内存无法释放。
-
记得即使置空
obj=null
5. 事件问题
-
在JS中,我们有时候会检测某个Element的变化,但是在检测过程中,如果没有及时对检测元素进行解绑操作,可能会造成内存泄漏
let ele = document.querySelect('ele'); ele.addEventListener('click', handle); let handle = function() { ...do some } -
以上代码中,没有对事件监听进行解绑操作,handle 会一直被占用,而不会进行垃圾回收。
-
document.body.removeChild('ele') ele.removeEventListener('click') ele = null;
vue的内存泄漏
- 在vue中,我们的操作,多少不注意也会存在内存泄漏
- vue官网给我们提供了避免内存泄漏的方法:
- 避免内存泄漏 — Vue.js (vuejs.org)
- 总之,记住及时清除定时器,在组件失效卸载前,要把全局引用、定时器等该干掉的都给干掉