8

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

我们都熟悉以下技术来阻止 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”对象为空(这是我们所期望的)。

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

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

如果有像上面这样的引用,那么赋值为空的修复不是依赖于实现吗?或者,它是 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()直接调用(暂时搁置设计和架构问题)?

也许我的思维过程工作方式不同,但是当他们第一次遇到 assignment-to-null 修复时,没有其他人再看一眼,其中元素引用为 null,但处理程序的代码仍然是执行?

4

3 回答 3

7

丢弃所有旧答案,并解决编辑问题:)

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

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

此代码将textContentof设置#fooabc,并丢弃对 的引用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保持对它的引用(因为我把它找回来调用一个方法 on 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();

如您所见,函数与我们分配它们的引用无关:它们与引用指向的对象相关联。您可以从对对象的任何引用中调用它们,而不仅仅是从您用来分配函数的引用中调用它们。

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

于 2011-06-13T04:58:42.127 回答
1

我远非 JavaScript 大师。但我想我至少可以通过提醒变量对象之间的区别来至少部分回答您的问题。让我们一次一行地浏览您的代码。

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

在这里,我们正在做两件事:

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

下一个:

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

在这里,我们使用变量 ele将回调分配给它引用的对象onclick(又是一个 DOM 元素)的事件。

最后:

ele = null;

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

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

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

这是我们刚刚分配ele的同一个变量null。因此,当调用此函数时——再次,因为onclick附加到对象的处理程序并没有消失——抛出空引用异常。

于 2011-06-13T05:05:38.180 回答
0

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

于 2011-06-13T04:58:20.627 回答