为什么 JavaScript 会出现内存泄漏?

发布于 2024-12-06 01:36:19 字数 1374 浏览 0 评论 0原文

我正在阅读这篇文章( http://www.ibm.com/developerworks/ web/library/wa-memleak/ )在 IBM 网站上关于 JavaScript 中的内存泄漏问题时,我遇到了一个看起来不太像泄漏的内存泄漏:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant
};
</script>
<button id="element">Click Me</button>
</body>
</html>

我理解所有其他泄漏,但这个泄漏脱颖而出。它说 DOM 和 JavaScript 对象之间存在循环引用,但我没有看到它。

有人能解释一下吗?

编辑:该链接似乎已被删除(我什至刷新了我打开的页面,但它已被删除)。这是 Google 的缓存(只要持续有效:http://webcache.googleusercontent.com/search?q=cache:kLR-FJUeKv0J:www.ibm.com/developerworks/web/library/wa-memleak/+memory+management+in+javascript& ;cd=1&hl=en&ct=clnk&gl=us&client=firefox-a )

I was reading this article ( http://www.ibm.com/developerworks/web/library/wa-memleak/ ) on IBM's website about memory leaks in JavaScript when I came across a memory leak that didn't quite look liked it leaked:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant
};
</script>
<button id="element">Click Me</button>
</body>
</html>

I understood all the other leaks but this one stood out. It says there's a circular reference between the DOM and JavaScript object but I don't see it.

Can anyone shed some light on this?

EDIT: The link seems to have been taken down (I even refreshed the page I had up and it was down). Here's Google's cache (for as long as that lasts: http://webcache.googleusercontent.com/search?q=cache:kLR-FJUeKv0J:www.ibm.com/developerworks/web/library/wa-memleak/+memory+management+in+javascript&cd=1&hl=en&ct=clnk&gl=us&client=firefox-a )

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

眼泪也成诗 2024-12-13 01:36:19

innerFunctiononclick 的赋值创建了一个函数闭包,该函数闭包保留 innerFunction 范围内的变量值。这允许 innerFunction 中的代码引用其上方的变量,并且是 javascript 的一个理想功能(其他一些语言没有)。

innerFunction 范围内的那些变量包括 obj。因此,只要存在对此闭包的引用,该闭包中的变量就会被保留。它是一次性的内存使用量,因此不会随着时间的推移而累积,因此通常并不重要。但是,如果您将大数据放入这些变量之一,然后期望它被释放,那么“是的”您将使用比您预期更多的浏览器内存。

在这个特定的示例中,这可能会导致问题,在 JS <==> 和 JS <==> 之间存在循环引用。 DOM。在 JS 中,您(在函数闭包中)在 obj 变量中保留了对 DOM 对象的引用。在 DOM 对象中,您保留了对 JS 代码的引用和函数闭包以及对 onclick 属性的赋值。一些较旧的浏览器足够愚蠢,即使您从 DOM 中删除“元素”对象,循环引用也会阻止垃圾收集器释放内存。这在现代浏览器中不是问题,因为它们足够聪明,可以看到此循环引用,并且如果没有外部引用,仍然可以释放该对象。

在您的特定代码中,您实际上并未创建泄漏,因为该元素仍在 DOM 中。 bigString 只是您附加到 DOM 元素的一大块数据,它将一直保留在那里,直到您删除该属性或删除 DOM 对象。这不是泄漏,这只是存储。

在 IE6 中,这会成为泄漏的唯一方法是如果您这样做:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant

};

// called later in your code
function freeObject() {
   var obj = document.getElementById("element");
   obj.parentNode.removeChild(obj);  // remove object from DOM
}

</script>
<button id="element">Click Me</button>
</body>
</html>

现在,您已经从 DOM 中删除了该对象(稍后在您的代码中),并且可能期望与其关联的所有内存都被释放,但是循环引用防止这种情况在 IE6 中发生。您可以通过执行以下操作来解决此问题:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant

};

// called later in your code
function freeObject() {
   var obj = document.getElementById("element");
   obj.onclick=null;                 // clear handler and closure reference
   obj.parentNode.removeChild(obj);  // remove object from DOM
}
</script>
<button id="element">Click Me</button>
</body>
</html>

或者,如果您不需要闭包中的 obj 引用,则可以使用以下方法完全使闭包中的引用为空:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant
   obj = null;   // kills the circular reference, obj will be null if you access if from innerFunction()
};
</script>
<button id="element">Click Me</button>
</body>
</html>

实际上,这是只有在少数情况下,当泄漏的内存使用量可能很大时,这才是有意义的问题。

  1. 如果您有大量数据存储为 DOM 属性,那么单个对象泄漏可能会泄漏大量数据。这通常可以通过不在 DOM 对象上存储大量数据来解决。将它们存储在 JS 中,您可以在其中显式控制生命周期。
  2. 如果您有一个长期存在的页面,其中包含大量 JavaScript 交互,并且创建/销毁 DOM 对象的操作可以执行很多很多次。例如,无人值守运行的幻灯片可能会一遍又一遍地创建/销毁 DOM 对象。即使每个项目的内存泄漏量很小,最终也会累积起来并导致问题。在我编写的幻灯片中,我只是确保没有在添加/删除的这些特定 DOM 对象上放置任何自定义属性,并且在从 DOM 中删除对象之前删除所有事件处理程序。这应该确保不会出现对它们的循环引用。
  3. 您在一个大循环中一遍又一遍地执行的任何类型的 DOM 操作。

The assignment to onclick of the innerFunction creates a function closure that preserves the value of the variables that are in scope of innerFunction. This allows the code in innerFunction to reference variables above it and is a desirable feature of javascript (something some other languages don't have).

Those variables that are in scope of innerFunction include obj. So, as long as there is a reference to this closure, the variables in that closure are preserved. It's a one-time piece of memory usage so it doesn't accumulate over time and thus isn't usually significant. But, if you put big data into one of those variables, then and expected it to be freed, then "yes" you would be using more browser memory than you expected.

Where this can cause problems is in this particular example, you have a circular reference between JS <==> DOM. In JS, you have preserved (in the function closure) a reference to the DOM object in the obj variable. In the DOM object, you have preserved a reference to the JS code and the function closure with the assignment to the onclick attribute. Some older browsers are dumb enough that even if you remove the "element" object from the DOM, the circular reference will keep the garbage collector from ever freeing the memory. This is not a problem in modern browsers as they are smart enough to see this circular reference and still free the object if there are no outside references to it.

In your particular code, you haven't actually created a leak because the element is still in the DOM. bigString is just a big chunk of data you've attached to the DOM element and it will stay there until you remove that attribute or remove the DOM object. That's not a leak, that's just storage.

The only way this would become a leak in IE6 is if you did this:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant

};

// called later in your code
function freeObject() {
   var obj = document.getElementById("element");
   obj.parentNode.removeChild(obj);  // remove object from DOM
}

</script>
<button id="element">Click Me</button>
</body>
</html>

Now, you've removed the object from the DOM (sometime later in your code) and probably expected all memory associated with it to be freed, but the circular reference keeps that from happening in IE6. You could work-around that by doing the following:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant

};

// called later in your code
function freeObject() {
   var obj = document.getElementById("element");
   obj.onclick=null;                 // clear handler and closure reference
   obj.parentNode.removeChild(obj);  // remove object from DOM
}
</script>
<button id="element">Click Me</button>
</body>
</html>

or, if you don't need the obj reference in the closure, you could null the reference from the closure entirely with this:

<html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
   var obj = document.getElementById("element");
   obj.onclick=function innerFunction(){
      alert("Hi! I will leak");
   };
   obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
   // This is used to make the leak significant
   obj = null;   // kills the circular reference, obj will be null if you access if from innerFunction()
};
</script>
<button id="element">Click Me</button>
</body>
</html>

In practice, this is only a meaningful issue in a few cases when the memory usage of a leak could be significant.

  1. If you have large chunks of data that you store as DOM attributes, then a single object leak could leak a large amount of data. That's usually solved by not storing large chunks of data on DOM objects. Store them in JS where you control the lifetime explicitly.
  2. If you have a long lived page with lots of javascript interaction and an operation that creates/destroys DOM objects can be done many, many times. For example, a slideshow that runs unattended may be creating/destroying DOM objects over and over and over again. Even small amounts of memory leakage per item could eventually add up and cause a problem. In a slideshow I wrote, I just made sure that I didn't put any custom attributes on these particular DOM objects that I was add/removing and that all event handlers were removed before removing the object from the DOM. This should assure that there could be no circular references to them.
  3. Any kind of DOM operation you're doing over and over in a big loop.
转身以后 2024-12-13 01:36:19

由于 IE6 错误,这会泄漏内存。

objonclick 引用 innerFunction,而 innerFunction 引用 obj 因为它捕获 obj 来自外部范围。

This leaks memory due to an IE6 bug.

obj references innerFunction from onclick, and innerFunction references obj because it captures obj from the outer scope.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文