本文是我看 How JavaScript Work 系列文章的学习笔记。
英文原文地址:https://blog.sessionstack.com/tagged/tutorial
中文翻译版地址:https://github.com/Troland/how-javascript-works
必须理解 JavaScript 的内存管理
虽然 JavaScript 看似是做到了自动释放内存,但其实这种自动的能力只是一种内存管理策略,代码编写不当必然会引起内存泄漏。所以想写好 JavaScript 必须理解内存管理。
内存的生命周期
内存生命周期一共分为3步,分别是:
- 分配内存
- 使用内存
- 释放内存
JavaScript 中的内存
分配内存
在 JavaScript 中分配内存的方式就是定义变量、对象或方法:
var n = 374; // 为数字分配内存
var s = 'sessionstack'; // 为字符串分配内存
var o = {
a: 1,
b: null
}; // 为对象及其值分配内存
var a = [1, null, 'str']; // (类似对象)为数组及其数组元素值分配内存
function f(a) {
return a + 3;
} // 分配一个函数(一个可调用对象)
// 函数表达式也分配一个对象
someElement.addEventListener('click', function() {
someElement.style.backgroundColor = 'blue';
}, false);
使用内存
使用内存就是对所定义的变量、对象或函数进行读写操作。读就是获取变量、写则是赋值变量、修改对象、函数传参等行为。
释放内存
释放内存有两种算法方案:引用计数和标记清除。
引用计数就是指记录内存被引用的次数,如果次数变为 0 则可以释放。
标记清除则复杂一些,下面是标记清除的逻辑:
- 根:一般来说,根指的是代码中引用的全局变量。就拿 JavaScript 来说,window 对象即是根的全局变量。Node.js 中相对应的变量为 "global"。垃圾回收器会构建出一份所有根变量的完整列表。
- 随后,算法会检测所有的根变量及他们的后代变量并标记它们为激活状态(表示它们不可回收)。任何根变量所到达不了的变量(或者对象等等)都会被标记为内存垃圾。
- 最后,垃圾回收器会释放所有非激活状态的内存片段然后返还给操作系统。
我理解就是通过遍历变量标记激活的变量,并且将失效的变量进行内存释放。
这两种方案中标记清除是 JavaScript 最常用的释放内存方案。
四种常见的 JavaScript 内存泄漏
- 全局变量 —— 由于 JavaScript 在非严格模式下会为未定义的变量定义一个全局变量,就会造成一些变量的内存泄漏。
- 定时器和被遗忘的回调函数 —— 回调函数作为函数必然是占用内存的,如果回调函数被定时器、事件监听器使用,函数所在内存就不会被释放。
- 闭包 —— 闭包能够长期持有函数内变量数据想必大家都知道,所以也要注意在不用闭包的时候释放内存。
- 源自 DOM 的引用
内存优化方案总结
- 使用 CSS3、SVG、IconFont、Canvas 替代图片。展示大量图片的页面,建议使用 Canvas 渲染而非直接使用img标签。具体详见 Javascript的Image对象、图像渲染与浏览器内存两三事。
- 适当压缩图片,可减小带宽消耗及图片内存占用。
- 使用恰当的图片尺寸,即响应式图片,为不同终端输出不同尺寸图片,勿使用原图缩小代替 ICON 等,比如一些图片服务如 OSS。
- 使用恰当的图片格式,如使用WebP格式等。详细图片格式对比,使用场景等建议查看web前端图片极限优化策略。
- 按需加载及按需渲染图片。
- 预加载图片时,切记要将 img 对象赋为 null,否则会导致图片内存无法释放。当实际渲染图片时,浏览器会从缓存中再次读取。
- 将离屏 img 对象赋为 null,src 赋为 null,督促浏览器及时回收内存及像素格式内存。
- 将非可视区域图片移除,需要时再次渲染。和按需渲染结合时实现很简单,切换 src 与 v-src 即可。
我只是个搬运工
声明下,本文是我的学习笔记,原汁原味还请查看原文
网友评论