JavaScript变量回收原则/垃圾回收机制
先看题目#
下列代码存在几个变量没有被回收?
var i = 1; var i = 2; var add = function() { var i = 0; return function() { i++; console.log(i); } }(); add();
答案
:3,全局变量有两个,即i和add,局部变量有一个i,因为局部变量被另一个作用域引用,所以局部变量i也不回收
var i = 1; // 全局变量不会被回收 var i = 2; // 这里重复声明变量i,因此var声明被忽略,只是把i赋值为2 var add = function() { // 全局变量不会被回收 var i = 0; // 局部变量 return function() { i++; console.log(i); // 被另一个作用域引用导致不会被回收 } }(); add();
变量回收原则#
-
全局变量不会被回收。
-
局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁。
-
只要被另外一个作用域所引用就不会被回收
JS的垃圾回收机制#
标记清除#
JS中最常见的垃圾回收方式是标记清除
标记清除的概念也好理解,从根部出发看是否能达到某个对象,如果能达到则认定这个对象还被需要,如果无法达到,则释放它,这个过程大致分为三步:
-
垃圾回收器创建roots列表,roots通常是代码中保留引用的全局变量,在js中,我们一般认定全局对象window作为root,也就是所谓的根部。
-
从根部出发检查所有 的roots,所有的children也会被递归检查,能从root到达的都会被标记为active。
-
未被标记为active的数据被认定为不再需要,垃圾回收器开始释放它们。
注意:
当一个对象零引用
时,我们从根部一定无法到达
。
但反过来,从根部无法到达
的不一定
是严格意义上的零引用
,比如循环引用,所以标记清除要更优于引用计数
。
引用计数#
工作原理:跟踪记录每个值被引用的次数。
什么情况会引起内存泄漏(无法释放已经不使用的内存)?
虽然有垃圾回收机制但是我们编写代码操作不当还是会造成内存泄漏。
-
意外的全局变量引起的内存泄漏。
原因:全局变量,不会被回收。
解决:使用严格模式避免。 -
闭包引起的内存泄漏
原因:闭包可以维持函数内局部变量,使其得不到释放。
解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。 -
没有清理的DOM元素引用
原因:虽然别的地方删除了,但是对象中还存在对dom的引用
解决:手动删除。 -
被遗忘的定时器或者回调
原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。
解决:手动删除定时器和dom。 -
子元素存在引用引起的内存泄漏
原因:div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。
解决:手动删除清空。