在js中我们声明的变量会占有一定的内存空间,正常情况下这些变量在代码执行完之后就会被gc回收,所占用的内存也会被释放,当某个变量不再被使用但是却没有被回收就会造成内存泄漏。
一、 全局变量
window上声明的变量,尤其是数据量很大的变量在不需要的时候要及时清除,否则会一直存在。
二、 dom引用
虽然删除了dom,但是由于被引用导致cotainer仍然在内存中无法被释放。这种情况要及时清除dom的引用:
const container = document.getElementById('container');
if(container){
container.parentNode.removeChild(container);
container = null; // 清除dom引用
}
三、闭包
闭包本身不会导致内存泄漏,当闭包使用完后没有清除引用,就会导致内存泄漏。
function foo(){
let count = 0;
return function(){
return count++;
}
}
const getCount = foo();
console.log(getCount()); // 0
// 后续不再使用了
getCount = null; // 要及时引用清除
四、setInterval
setInterval在使用完之后要及时清除,除此之有些场景要在执行到一定时间或一定次数之后清除掉定时器,否则会一直执行,使回调函数里面的引用无法释放,造成内存泄漏。
let count = 0;
this.timer = setInterval(()=>{
const container = document.getElementById("container");
if (container) {
container.innerHTML = "hello world";
clearInterval(this.timer);
}else if(count === 20){ // 2秒后清除定时器
clearInterval(this.timer);
}
count++;
},100);
五、setTimeout
如果回调函数中引入了外部的变量,直到回调函数执行完之前,这些变量会一直被引用无法被gc回收。最常见的就是react中使用了this,组件卸载的时候this仍然被引用,导致内存泄漏。这种情况要在组件卸载之前将this设置为null。
setTimeout在重复调用的时候(遍历、递归、组件声明周期等)每次调用之前要先清除之前的定时器,否则会同时存在多个定时器造成内存泄露
typeWords = (words, container) => {
let index = 0;
let timer;
const type = (text) => {
if (index< text.length) {
clearTimeout(timer); // 声明新的定时器之前先清除掉旧的
timer = setTimeout(() => {
container.innerHTML += words.charAt(index);
index++;
type(text);
}, 100);
}
};
if (container) type(words);
};
在react的周期函数中使用定时器要格外小心,尤其是在didUpdate的时候,每次组件更新都会产生一个新的setTimeout而componentWillUnmount中只能清除最后一个,这种情况要在声明新的setTimeout之后清除掉旧的。
componentDidUpdate(){
clearTimeout(this.timer); //清除定时器,
this.timer = setTimeout(() => {
console.log('componentDidUpdate')
}, 200);
}
componentWillUnmount() {
clearTimeout(this.timer); //清除定时器,
}
六、 ReactDom.render
使用ReactDom.render渲染组件的时候,当组件卸载的时候并不会卸载组件,组件在内存中依然存在,要调用ReactDom.unmountComponentAtNode(container)方法手动卸载组件。
setTimeout(() => {
ReactDOM.render(<App />, document.getElementById("root"));
}, 1000);
七、 事件监听
事件注册分为三种:dom事件、全局事件、自定义事件。不管是哪种事件在使用完之后都要取消掉。
componentDidMount() {
const container = document.getElementById("container");
if (container) {
container.addEventListener('click', this.click, true);
}
window.addEventListener('resize', this.resize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
conatiner.removeEventListener('click', this.click, true);
}
需要注意的是在取消事件监听的时候,事件类型、事件、参数要完全一致。 一些三方库的事件机制中会自动帮我们取消事件监听如EventEmitter中的once和react中的事件等。