Утечки памяти в Javascript: почему назначение объекта на null работает? - PullRequest
6 голосов
/ 13 июня 2011

Может ли кто-нибудь поцарапать меня из-за характера исправления присвоения-нуля, используемого для предотвращения утечек памяти?

Мы все знакомы со следующей техникой, чтобы остановить циклическую ссылку между объектом DOM и объектом JS, чтобы предотвратить утечки памяти:

    function foo() {
      var ele = document.getElementById("someParagraphId");

      ele.onclick = function() {
        //some action here
      };

      ele = null;
    }

Вопрос в том, почему вышеперечисленное сработает? Установка «ele» в ноль определенно остановит циклические ссылки, но разве это не помешает будущим ссылкам на «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.

Насколько я понимаю, это исправление всегда было безопасным для разных браузеров. Я не сталкивался со многими примерами, в которых бы упоминались конкретные сведения об обнаружении / прослушивании типа браузера перед применением нулевого назначения.

=============== EDIT ==================

Я думаю, из-за того, как я изложил этот вопрос, возможно, невольно возникла непосредственная дискуссия из того, что я пытался донести. Пара понятий, на которые ссылаются:

дескрипторы объектов / ссылки на объекты 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 Engine вызывать ele.onclick() напрямую (оставляя в стороне проблемы дизайна и архитектуры)?

Возможно, мои мыслительные процессы работают по-другому, но никто больше не взглянул во второй раз, когда впервые столкнулся с исправлением присваивания-нуля, где ссылка на элемент была нулевой, и все же код для обработчика все еще был казнен?

Ответы [ 3 ]

6 голосов
/ 13 июня 2011

Уничтожение всего старого ответа и обращение к редактированию:)

Давайте рассмотрим более простой пример: textContent.

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

Этот код устанавливает textContent из #foo в 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();

Как видите, функции не привязаны к ссылкам, на которые мы их присваиваем: они привязаны к объектам, на которые указывают ссылки. Вы можете вызывать их из любой ссылки на объект, а не только из той, которую вы использовали для назначения функции.

Таким образом, вы можете аннулировать ссылки, чтобы нарушать круговые зависимости, не влияя на правильное поведение объектов.

0 голосов
/ 13 июня 2011

Я далеко не гуру 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; оно не изменилось.

Тот факт, что обработчик все еще вызывается, является правильным поведением. Теперь, что касается ошибки, давайте еще раз посмотрим на функцию, назначенную событию onclick этого элемента DOM:

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

Эта ele - это та же самая переменная , которую мы только что присвоили null. И поэтому, когда эта функция вызывается - потому что снова обработчик onclick, присоединенный к объекту , не исчез - генерируется исключение нулевой ссылки.

0 голосов
/ 13 июня 2011

Поскольку вы получаете ele во-первых, вызывая document.getElementById("someParagraphId"), объект, на который он ссылается, уже существует в памяти, прежде чем назначить ele.Это часть документа - конечно, в памяти есть ссылка на него, отличная от ele.

...