内存爆掉的页面
内存泄漏(Memory leak)
定义:当进程不再需要某些内存的时候,这些不再被需要的内存依然没有(不能)被进程回收
js中常见的内存溢出
意外的全局变量
function foo() {
bar = 1
}
function foo() {
bar = [...Array(1000000).keys()]
}
foo()
setTimeout(() => {
bar = null
}, 5000);
错误的闭包写法造成
function fn () {
var a = 'a'
return function () {
console.log(a)
};
}
const bar = fn()
bar()
function foo() {
var temp_object = new Object()
temp_object.x = 1
temp_object.y = 2
temp_object.array = [...Array(1000000).keys()]
const temp = temp_object.x
return function () {
console.log(temp)
}
}
const bar = foo()
bar()
function foo() {
var temp_object = new Object()
temp_object.x = 1
temp_object.y = 2
temp_object.array = [...Array(1000000).keys()]
return function () {
console.log(temp_object.x)
}
}
const bar = foo()
bar()
未删除的js dom
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="create">新增</button>
<script>
var detachedTree;
function create() {
var ul = document.createElement('ul');
for (var i = 0; i < 10; i++) {
var li = document.createElement('li');
ul.appendChild(li);
}
detachedTree = ul;
}
document.getElementById('create').addEventListener('click', create);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="rmListNode">删除列表</button>
<button id="toNull">删除js引用</button>
<script>
let detachedUlTree = document.createElement('ul')
for (let i = 0; i < 30000; i++) {
let li = document.createElement('li')
li.innerText = i
detachedUlTree.appendChild(li)
}
document.body.appendChild(detachedUlTree)
document.querySelector('#rmListNode').addEventListener('click', function() {
document.body.removeChild(detachedUlTree)
})
document.querySelector('#toNull').addEventListener('click', function() {
document.body.removeChild(detachedUlTree)
detachedUlTree = null
})
</script>
</body>
</html>
被遗忘的定时器
setInterval(function () {
var renderer = document.getElementById('renderer')
if (renderer) {
// do something
}
}, 5000)
document.querySelector('btn').addEventListener('click', function() {
document.body.removeChild(document.getElementById('renderer'))
// 应该把定时器保存成变量,在这里用clearInterval清理掉
})
<template>
<div></div>
</template>
<script>
export default {
methods: {
refresh() {
// 获取一些数据
},
},
mounted() {
setInterval(function() {
// 轮询获取数据
this.refresh()
}, 2000)
},
}
</script>
组件销毁后未删除定时器,定时器还在定时执行,正确的写法
<template>
<div></div>
</template>
<script>
export default {
methods: {
refresh() {
},
},
mounted() {
this.refreshInterval = setInterval(function() {
this.refresh()
}, 2000)
},
beforeDestroy() {
clearInterval(this.refreshInterval)
},
}
</script>
被遗忘的事件监听
var renderer = document.getElementById('renderer')
function callBack() {
var $btn = document.querySelector('btn')
if ($btn) {
console.log($btn.innerHTML())
}
}
renderer.addEventListener('click', callBack)
document.querySelector('deleteBtn').addEventListener('click', function() {
document.body.removeChild(document.getElementById('btn'))
// renderer.removeEventListener(callBack)
})
<script>
export default {
mounted () {
window.addEventListener('resize', this.func)
}
}
</script>
组件销毁时应删除时间绑定
mounted () {
window.addEventListener('resize', this.func)
},
beforeDestroy () {
window.removeEventListener('resize', this.func)
}
错误的使用map set等(set、map、weakMap、weakSet区别)
key不会被垃圾回收
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
key = null;
正确的写法:
let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
map.delete(key);
key = null;
或
let map = new WeakMap();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
key = null;
浏览器内存问题分析思路:
- 借助性能分析工具Performance、performance monitor分析主要问题
- 使用Memory定位具体的问题点
相关的重要概念
Shallow size(浅层大小)和 Retained size(保留的大小)(参考)
- 这里是列表文本浅层大小:对象本身的大小
- 这里是列表文本保留的大小:在删除对象本身及其从GC根不可达的依赖对象时释放的内存大小
GC roots
在浏览器环境中,GC Root 有很多,通常包括了以下几种 (但是不止于这几种):
- 全局的 window 对象(位于每个 iframe 中)
- 文档 DOM 树,由可以通过遍历文档到达的所有原生 DOM 节点组成
- 存放调用栈上变量
调用栈
function foo() {
var a = '极客时间'
var b = a
var c = { name: '极客时间' }
var d = c
debugger
}
foo()
垃圾回收算法(标记清除、V8实现):
- 这里是列表文本通过 GC Roots 标记空间中活动对象和非活动对象。通过 GC Root 遍历到的对象,我们就认为该对象是可访问的(reachable),是活动对象;通过 GC Roots 没有遍历到的对象,则是不可访问的(unreachable),那么这些不可访问的对象就可能被回收
- 回收非活动对象所占据的内存
- 做内存碎片整理
此处简单介绍,详细过于复杂,感兴趣可自行学习极客时间图解google V8第20、21、22小节
参考文档
图解 Google V8
浏览器工作原理与实践
google devtools doc:Fix memory problems
使用 chrome-devtools Memory 面板
万恶的前端内存泄漏及万善的解决方案
深入了解 JavaScript 内存泄露
你不知道的 WeakMap
原来 JavaScript 中的 WeakMap 是这样子的