什么是“垃圾”
1. 数据可达性
- 如果一段数据是可达的(reachability), 那么我们称之为有用数据;反之如果不可达,则是无用数据,也就是垃圾。
2. 根
当一个值满足以下几种情况之一时,这个值称之为根数据(roots):
- 全局变量;
- 当前函数当原有变量和参数;
- 当前链中其他函数的变量和参数;
- 其他内部变量
3. 垃圾回收
- 如果数据既不是根数据,也没有引用到根数据,那么该数据不可达,即需要被 JS 引擎回收;
- 垃圾回收是 JS 引擎的一个后台进程。引擎会监控所有的对象,删去不可达的数据。
- 垃圾回收的意义在于:重复利用无用的内存。
- 🌰 举个例子:
let user = { name: "John" };
let admin = user; // 关系如下图
let user = null; // 如果只加这一行,该对象不是垃圾,因为它还被admin还引用了
let admin = null; // 如果同时写这两行,那么该对象没有引用到根数据,会被垃圾回收
- 🌰 🌰 再举个例子:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return { father: man, mother: woman }
}
let family = marry({ name: "John" }, { name: "Ann" });
(1) John和Ann是夫妻,组成了一个家庭family(引用了全局变量),如果他们有孩子,他们将会成为父亲和母亲;
(2) 当家庭把John除名以后,家庭不承认John的父亲身份,Ann也不承认John是她的丈夫(如下图)
(3) 那么此时,虽然John单方面认为Ann是他的妻子,但是因为family和Ann都没有引用John(不承认和John有关系),此时的John虽然引用了Ann,却是一个不可达的数据,会被 JS 引擎回收掉~
(4) 如果John和Ann互相都承认夫妻关系,也承认他们组建的家庭,但是他们的家庭不被全局变量承认(如下图,当family:null时⬇️ ),那么很遗憾,这个家庭的数据全部都会被回收掉
如何“捡垃圾”
- 从全局变量出发,标记引用了根数据的对象(可用遍历或计数的方法);
- 遍历后,没有被遍历的对象为不可达对象(unreachables),会被回收;或使用计数法,没有引用指向该对象,或者说引用数为0的,就是不可达对象
- 遍历/计数时,要注意引用(箭头)的方向,只有根数据指向的对象,才是可达的(reachable)(如下图)
小结
- 垃圾回收是自动运行的,我们无法操作它的开始与结束;
- 可达的数据会被保存;
- “被引用”和“从根数据可达”是不一样的,一段“被引用”的数据,可能是“不可达”的 (Being referenced is not the same as being reachable)。
参考文章 | Reference List
The JavaScript language: javascript.info/garbage-col…
MDN 内存管理: developer.mozilla.org/zh-CN/docs/…