Javascript内存泄漏:为什么将对象分配给null会起作用

Javascript memory leaks: why would assigning object to null work?

本文关键字:分配 null 起作用 对象 内存 泄漏 为什么 Javascript      更新时间:2023-09-26

有人能为我挠痒痒吗?关于用于防止内存泄漏的null修复赋值的性质?

我们都熟悉以下技术来停止DOM对象和JS对象之间的循环引用,以防止内存泄漏:

    function foo() {
      var ele = document.getElementById("someParagraphId");
      ele.onclick = function() {
        //some action here
      };
      ele = null;
    }

问题是,为什么上述方法会奏效?将"ele"设置为null肯定会停止循环引用,但这不是也会阻止将来对"ele"的引用吗?

    function foo() {
      var ele = document.getElementById("someParagraphId");
          ele.onclick = function() {
              console.log("accessing ele ... after set to null " + ele.onclick);
          };
      ele = null;
    }

然而事件侦听器却启动了。它会抱怨"ele"对象为null(这正是我们所期望的)。

考虑到上述行为,我们是否可以推断Javascript引擎实现将持有对事件侦听器的某种内部引用,并且在触发事件时调用的正是这种引用?

eventHandlerRef = //assignment to "ele.onclick" handler/listener;

如果有像上面这样的引用,那么对null fix的赋值不是依赖于实现吗?或者,它是ECMAScript规范的一部分吗。

据我所知,这个修复程序一直是跨浏览器安全的。我没有遇到过很多具体提到在应用null赋值之前检测/嗅探浏览器类型的例子。

================编辑================

我想,由于我提出这个问题的方式,我可能无意中直接从我试图传达的内容中引发了讨论。引用的几个概念:

对象句柄/对象引用ala:

 var obj1, obj2;
     obj1 = {foo: "bar"}; //obj1 points to the an area of the heap allocated 
                          //for the object literal.
 obj2 = obj1;     //obj2 points to the same position in the heap
                      //as obj1.
 //obj1 = null or
 obj1 = {/*another obj literal*/}  //the new object literal is created 
                                       //and allocated in some other part 
                                       //in the heap, and obj1 now points 
                                       //to the new location.
//obj2 is unaffected by obj1's re-assignment. 

以上并不是我的痛处,我很后悔加了一行:

console.log("accessing ele ... after set to null " + ele.onclick);

以上内容使这看起来像是一个结束问题。我完全预料到错误会像原来的帖子中所说的那样抛出。

我的瘙痒更多的是。。。出于某种原因,在我看来,Javascript引擎会在事件触发时直接调用ele.onlick(),将元素设置为null类似于以下流程:

var obj = {};
obj.method = function() {};
obj = null;
//error calling obj.method(), i.e., obj is null

假设我们在最初的帖子中知道,在ele设置为null后,事件处理程序仍然会触发,在我看来,流程更类似于:

var obj = {};
obj.method = function() {};
var objRef = obj; 
obj = null;  
//The Javascript Engine calling objRef.method when event triggered.

我的问题归结为,上面是大多数Javascript实现的工作方式吗?其中一些内部引用指向指定的事件处理程序,而当事件被触发时,这个内部引用是被调用的吗?

换句话说,是什么阻止Javascript引擎实现直接调用ele.onclick()(暂时不考虑设计和体系结构问题)?

也许我的思维过程工作方式不同,但当其他人第一次遇到null fix的赋值时,他们没有再看一眼吗?在这里,元素引用是null ed,但处理程序的代码仍然在执行?

丢弃所有旧答案,并处理编辑:)

让我们举一个更简单的例子:textContent

var ele = document.getElementById('foo');
ele.textContent = 'abc';
ele = null;

此代码将#footextContent设置为abc,并放弃对ele的引用。现在,如果我再次请求#foo,会发生什么?。。。

var ele = document.getElementById('foo');
ele.textContent = 'abc';
ele = null;
ele = document.getElementById('foo');
console.log('#foo is ' + ele.textContent);

它将记录"#foo is abc"。这只是表明#foo存在于我的脚本之外,并且document保留了对它的引用(因为我在document上调用了一个方法)。它与事件处理程序的工作原理相同。

var ele = document.getElementById('foo');
ele.onclick = function() { console.log('abc'); };
ele = null;
ele = document.getElementById('foo');
console.log('#foo.onclick is ' + ele.onclick);

事件处理程序不是一种特殊的属性。它们只是变量,您可以在上面编写(对函数的)引用。这些大致是函数语言特性,其中函数可以用作简单值。JavaScript实现只是从DOM引用而不是ele引用调用事件处理程序。让我们再举一个简单的例子,不使用document.getElementById

var a = {};
var b = a;
b.foo = function() { console.log("hello world!"); };
console.log(a.foo + " is the same as " + b.foo);
b = null;
console.log("Even though b is " + b + ", a.foo is still " + a.foo);
a.foo();

正如你所看到的,函数并没有绑定到我们分配它们的引用:它们绑定到引用指向的对象。你可以从对对象的任何引用中调用它们,而不仅仅是从你用来分配函数的引用中调用。

因此,您可以在不影响对象正确行为的情况下取消引用以打破循环依赖关系。

我远非JavaScript大师;但我认为,只要提醒变量对象之间的区别,我至少可以部分回答您的问题。让我们一次一行地遍历您的代码。

var ele = document.getElementById("someParagraphId");

我们在这里做两件事:

  • 声明变量ele
  • 将对DOM元素(对象)的引用分配给该变量

下一篇:

ele.onclick = function() {
    console.log("accessing ele ... after set to null " + ele.onclick);
};

在这里,我们使用变量ele为其引用的对象(同样是DOM元素)的onclick事件分配回调。

最后:

ele = null;

最后,我们为ele变量分配了一个null引用。它不再引用刚才所做的对象但是,该对象仍然存在,具有相同的onclick处理程序;它没有改变

处理程序仍然被调用的事实是正确的行为。现在,关于这个错误,让我们再看一下分配给该DOM元素的onclick事件的函数:

console.log("accessing ele ... after set to null " + ele.onclick);

这个ele与我们刚刚分配给null变量完全相同。因此,当调用此函数时——同样,因为连接到对象onclick处理程序没有消失——会引发null引用异常。

由于首先通过调用document.getElementById("someParagraphId")获得ele,因此在分配ele之前,它引用的对象已经存在于内存中。它是文档的一部分——当然,除了ele之外,内存中还有对它的引用。