关于javascript:DOM:为什么这是内存泄漏?

DOM: why is this a memory leak?

请考虑Mozilla Docs关于JavaScript内存泄漏的这句话:

1
2
3
4
5
6
function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
}

The above code sets up the element to turn red when it is clicked. It
also creates a memory leak. Why? Because the reference to el is
inadvertently caught in the closure created for the anonymous inner
function. This creates a circular reference between a JavaScript
object (the function) and a native object (el).

请以简单明了的方式解释上述泄漏的原因,我没有得到确切的观点。

由于泄漏,网站/页面是否面临安全问题? 我该如何避免它们? 其他什么代码会导致内存泄漏? 如何判断内存泄漏的时间?

我是内存泄漏主题的绝对新手。 有人可以一步一步地为我澄清这些东西吗?还有人可以帮我澄清这句话"这会在JavaScript对象(函数)和本机对象(el)之间创建一个循环引用。"


有两个概念可以帮助您理解这个示例。

1)关闭

闭包的定义是每个内部函数都可以访问其父函数变量和参数。

addHandler()函数完成时,匿名函数仍然可以访问父变量el

2)功能=记忆

每次定义function时,都会创建一个新对象。
是什么让这个例子有点混乱,onclick是一个只能设置为DOM元素一次的事件。

那么el.onclick = function(){};肯定会覆盖旧功能吗?

错误!每次addHandler运行时,都会创建一个新的函数对象。

结论:

每次函数运行时,它都会创建一个新对象,其中包含一个包含el的闭包。看到匿名函数维护对el的访问,垃圾收集器无法将其从内存中删除。

anon函数将保持对el的访问,并且el可以访问该函数,即循环引用,这会导致IE中的内存泄漏。


无论何时在JavaScript中定义函数,都会为其创建执行上下文;此执行上下文包含对作用域链中所有变量的引用,从全局作用域一直到本地作用域:

1
2
3
4
5
6
7
8
function test()
{
    var el = document.getElementById('el');
    el.onclick = function() {
        // execution context of this function: el, test
        alert('hello world');
    }
}

test()完成时,匿名函数还没有被回收,因为它现在被分配给DOM的一个元素;即它被DOM元素的属性引用。

同时,DOM元素本身也是函数执行上下文的一部分,现在由于循环引用而无法再循环,即使实际使用它并不是很明显;你可以在这个答案中找到一个演示。

也就是说,如今,大多数JavaScript引擎(甚至是那些在IE中找到的引擎)使用更高级的垃圾收集器,可以使用标记和清除或生成/短暂垃圾收集等技术更好地识别未使用的变量。

为了确保您不会在任何浏览器上遇到问题(但是,由于页面的典型生命周期,这主要是理论上的):

1
2
3
document.getElementById('el').onclick = function() {
    alert('hello world');
}


另请参阅有关该问题的MS文章的更多信息部分:

This memory leak occurs because DOM objects are non-JScript objects.
DOM objects are not in the mark-and-sweep garbage collection scheme of
JScript. Therefore, the circular reference between the DOM objects and
the JScript handlers will not be broken until the browser completely
tears down the page.

但请注意,与该文章中所述(内存将在浏览器转到新页面时回收)相反,本文确认IE 6中的错误导致内存永久泄露。


JavaScript的内存管理通常是这样的:"只要有可能达到它,就保持它"。这基本上是任何垃圾收集驱动的内存模型背后的范例。

垃圾收集器往往非常擅长他们的工作,他们甚至可以检测某组元素是否只能在这组元素中到达。这些组也被称为循环引用,因为如果你按照引用,你最终会遇到你已经访问过的元素:你已经运行了一个圆圈。

但是,在您的示例中,您实际上有两个来自两个不同"世界"的对象:

Circular references

Internet Explorer uses its own garbage collection scheme for this, separate from the mechanism used by JavaScript. It is the interaction between the two that can cause memory leaks.

这正是发生的事情,可能导致内存泄漏。