JavaScript 是使用垃圾回收的语言,也就是说执行环境负责在代码执行时管理内存。在 C 和 C++等 语言中,跟踪内存使用对开发者来说是个很大的负担,也是很多问题的来源。
JavaScript 为开发者卸下 了这个负担,通过自动内存管理实现内存分配和闲置资源回收。基本思路很简单:确定哪个变量不会再 使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间(或者说在代码执 行过程中某个预定的收集时间)就会自动运行。
垃圾回收过程是一个近似且不完美的方案,因为某块内存是否还有用,属于“不可判定的”问题,意味着靠算法是解决不了的。 我们以函数中局部变量的正常生命周期为例。函数中的局部变量会在函数执行时存在。
此时,栈(或堆)内存会分配空间以保存相应的值。函数在内部使用了变量,然后退出。此时,就不再需要那个局部 变量了,它占用的内存可以释放,供后面使用。这种情况下显然不再需要局部变量了,但并不是所有时 候都会这么明显。
垃圾回收程序必须跟踪记录哪个变量还会使用,以及哪个变量不会再使用,以便回收 内存。如何标记未使用的变量也许有不同的实现方式。不过,在浏览器的发展史上,用到过两种主要的 标记策略:标记清理和引用计数。
1. 引用计数:
常用的垃圾回收策略是引用计数(reference counting)。其思路是对每个值都记录它被 引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变 量,那么引用数加 1。
类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一 个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序 下次运行的时候就会释放引用数为 0 的值的内存。
引用计数最早由 Netscape Navigator 3.0 采用,但很快就遇到了严重的问题:循环引用。所谓循环引 用,就是对象 A 有一个指针指向对象 B,而对象 B 也引用了对象 A。
objectA 和 objectB 通过各自的属性相互引用,意味着它们的引用数都是 2。在 标记清理策略下,这不是问题,因为在函数结束后,这两个对象都不在作用域中。
objectA 和 objectB 在函数结束后还会存在,因为它们的引用数永远不会变成 0。如果函数被多次调 用,则会导致大量内存永远不会被释放。为此,Netscape 在 4.0 版放弃了引用计数,转而采用标记清理。 事实上,引用计数策略的问题还不止于此。
BOM 和 DOM 中的对象是 C++ 实现的组件对象模型(COM,Component Object Model)对象,而 COM 对象使用引用计数实现垃圾回 收。因此,即使这些版本 IE 的 JavaScript 引擎使用标记清理,JavaScript 存取的 COM 对象依旧使用引用 计数。换句话说,只要涉及 COM 对象,就无法避开循环引用问题。下面这个简单的例子展示了涉及 COM 对象的循环引用问题:
let element = document.getElementById("some_element");
let myObject = new Object();
myObject.element = element;
element.someObject = myObject;
这个例子在一个 DOM 对象(element)和一个原生 JavaScript 对象(myObject)之间制造了循环 引用。myObject 变量有一个名为 element 的属性指向 DOM 对象 element,而 element 对象有一个 someObject 属性指回 myObject 对象。由于存在循环引用,因此 DOM 元素的内存永远不会被回收, 即使它已经被从页面上删除了也是如此。 为避免类似的循环引用问题,应该在确保不使用的情况下切断原生 JavaScript 对象与 DOM 元素之 间的连接。比如,通过以下代码可以清除前面的例子中建立的循环引用:
myObject.element = null;
element.someObject = null;
把变量设置为 null 实际上会切断变量与其之前引用值之间的关系。当下次垃圾回收程序运行时, 这些值就会被删除,内存也会被回收。 为了补救这一点,IE9 把 BOM 和 DOM 对象都改成了 JavaScript 对象,这同时也避免了由于存在两 套垃圾回收算法而导致的问题,还消除了常见的内存泄漏现象。
标签:垃圾,对象,回收,element,myObject,引用,js,内存 From: https://blog.51cto.com/u_16273048/9166576