这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战**
最近在写小程序的过程中,用到了很多的console.log,开始是为了联调方便,一直没有删除,导致页面时不时有卡顿现象出现,开始觉得页面重新刷新就好了,但是产品和测试不会答应的呀,后来排查了一下问题,才发现页面中出现很多种内存泄漏,想起之前面试时候这个问题也是考了很多次,一直没有总结,今天就趁机总结一下也好提醒自己平时多多注意。
什么是内存泄漏?
首先我们得清楚什么是内存泄漏,在引擎中有垃圾回收机制,主要针对一些程序中不再使用的对象,清理回收释放掉内存,但是实际上垃圾回收机制并不会把不再使用的对象全部回收掉。因此我们在代码中要主动避免一些不利于引擎做垃圾回收的操作,这些没有被及时回收的对象内存,我们叫它内存泄漏(Memory Leak)。
常见的内存泄漏
来看看一些常见的内存泄漏案例:
不正当的闭包
大部分认为闭包就是函数内部嵌套并return一个函数,我翻了翻几本书中的描述:
- JavaScript高级程序设计:闭包是指有权访问另一个函数作用域中的变量的函数。
- JavaScript权威指南:从技术的角度讲,所有的JavaScript函数都是闭包,他们都是对象,都关联到作用域链。
- 你不知道的JavaScript:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
闭包涉及的范围是比较广了,现在看一个普遍的例子再认识一下闭包:
function fnOne(){
let test = new Array(1000).fill('www')
return function(){
console.log('hahaha')
}
}
let fn1Child = fnOne()
fn1Child()
这里因为return的函数中存在函数fnOne中的test变量引用,test不会被回收,也就造成了内存泄漏。
怎么解决呢?就是在函数调用后,把外部的引用关系置空就好了:
function fnOne(){
let test = new Array(1000).fill('www')
return function(){
console.log(test)
return test
}
}
let fn1Child = fnOne();
fn1Child()
fn1Child = null;
减少使用闭包,不正当的使用闭包可能会造成内存泄漏。
隐式全局变量
在JavaScript中,垃圾回收是自动执行的,但是对于全局变量,垃圾回收器很难判断这些变量什么时候才不被需要,所以全局变量不会被回收,这时候就产生了内存泄漏:
function fn(){
// 没有声明从而制造了隐式全局变量test1
test1 = new Array(1000).fill('isboyjc1')
// 函数内部this指向window,制造了隐式全局变量test2
this.test2 = new Array(1000).fill('isboyjc2')
}
fn()
遗忘的定时器
let someResource = getData()
setInterval(() => {
const node = document.getElementById('Node')
if(node) {
node.innerHTML = JSON.stringify(someResource))
}
}, 1000)
代码中,每隔一秒就把得到的数据放入到node节点中,但是在setInterval没有结束之前,回调函数里的变量以及回调函数本身都无法被回收。这时候需要用到clearInterval来清理这个定时器,才能回收someResource。
setTiemout也是同样的道理,不需要时候,及时去清除。
遗忘的事件监听器
在代码中我们会用到事件监听器,在组件内挂载相关的函数,组件销毁时候不主动清除时,其中的变量和函数会被认为是需要的,就不会被回收,这些内部引用的变量存储了大量数据,就会引起页面占用内存过高,造成内存泄漏。
遗忘的监听者模式
不管是Vue还是React,监听者模式实现一些消息通信是 很普遍的,比如EventBus,当我们实现了监听者模式在组件内挂载相关的函数,组件销毁时候不主动清除,就同样不会进行回收。这时候我们要在beforeDestroy 组件销毁生命周期里及时去清除。
未清理的Console输出
最后就是我们在联调时候常见的console.log了,我们在浏览器控制台可以看到数据输出,输出对象的时候就也造成了内存泄漏。
差不多说完了常见的内存泄漏,下一篇就讲一下怎么排查定位代码中是否存在内存泄漏。