Javascript, круговые ссылки и утечки памяти

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

Это все еще в последних браузерах? (например, Chrome, FF 3.5 и т.д.)

Ответ 1

Подавляющее большинство утечек, о которых мы говорим в JavaScript, особенно в IE6-7, когда вы создаете опорный цикл между объектами JavaScript и объектами хоста, такими как узлы DOM.

В IE6 это особенно пагубно, поскольку вы не получаете память обратно, когда покидаете страницу; он исчез, пока вы не покинете браузер. В IE7, очищающем страницу, теперь возвращает память, но у вас все еще может быть трудность, когда у вас есть многолетнее приложение. IE8 решает эту проблему должным образом, превращая узлы DOM в собственные объекты JavaScript вместо объектов хоста. (Вы все еще можете запускать утечки в IE8, включив в него нулевые объекты, такие как объекты ActiveX).

Конечно, все еще будут небольшие неясные утечки памяти, скрывающиеся в случайных местах для всех браузеров, особенно в более старых версиях. Но нет никакого способа легко классифицировать и избегать их, как с проблемой IE refloop.

Ответ 2

Чтобы добавить к запросу bobince, я провел несколько тестов с IE8.

Я попробовал почти все примеры, представленные в http://www.javascriptkit.com/javatutors/closuresleak/index.shtml

Никто из них больше не пропускает память (по крайней мере, не воспринимается), за исключением того, что удаляет дочерние узлы с событиями, все еще привязанными к ним.

Этот тип примера, я думаю, лучше объяснил Дуглас Крокфорд в своем queuetest2.

Этот все еще теряет память на IE8, и это довольно легко проверить, просто запустив тест script и глядя на диспетчер задач Windows - производительность - использование PF. Вы увидите, что использование PF увеличивается почти на 1 МБ за цикл (очень быстро).

Но в IE8 память выпущена на странице unload (например, переход на новую страницу или перезагрузка той же страницы) и, очевидно, также при полном закрытии браузера.

Итак, для того, чтобы конечный пользователь мог воспринимать эти утечки памяти на IE8 (как уменьшенные результаты systerm), ему нужно долгое время оставаться на одной странице, что в наши дни часто может случиться с AJAX, но это странице нужно также сделать сотни дочерних элементов удаления элементов с прикрепленным к ним событием.

Тест Дугласа Крокфорда подчеркивает браузер с добавлением 10000 узлов, а затем удаляется, что отлично подходит для показа вам проблемы, но в реальной жизни у меня никогда не было страницы, на которой было удалено более 10 элементов. INMHO обычно быстрее использует display: none вместо удаления всего набора узлов, поэтому я не очень часто использую removeChild.


Для тех, кто больше интересуется утечкой памяти IE8, описанной выше, я сделал еще один тест, и кажется, что mem-утечки вообще не отображаются в IE8 при использовании innerHTML вместо appendChild/removeChild добавить/удалить дочерние элементы с прикрепленными событиями. Поэтому, по-видимому, Douglas Crockford > (предложенная им для предотвращения утечек памяти в IE) больше не нужна в IE8, по крайней мере, при использовании innerHTML...

(EDITED благодаря комментарию 4esn0k ниже) ... кроме того, Douglas Crockford функция очистки НЕ работает вообще на IE8, в своем коде var a = d.attributes возвращает NO onclick атрибуты (или любые другие атрибуты onevent), которые были добавлены во время выполнения в IE8 (они возвращаются в IE7).

Дуглас Крокфорд говорит:

"Перед удалением любого элемента следует вызвать функцию очистки методом removeChild или устанавливая свойство innerHTML.

Здесь я предоставляю код для теста:

<body>    
   <p>queuetest2 similar to the one provided by Douglas Crockford
   at http://www.crockford.com/javascript/memory/leak.html
   <br>but this one adds/removes spans using innerHTML
   instead of appendChild/removeChild.</p>

   <div id="test"></div>    
   <script>
       /* ORIGINAL queuetest2 CODE FROM DOUGLAS CROCKFORD IS HERE
          http://www.crockford.com/javascript/memory/queuetest2.html */

      (function (limit, delay) 
      {
          var n = 0;
          var add = true;

          function makeSpan(n) 
          {
              var div = document.getElementById('test');
              //adding also an inline event to stress more the browser
              div.innerHTML = "<span onmouseover=\"this.style.color = '#00ff00';\">" + n + "</span>";
              var s = div.getElementsByTagName('span')[0];
              s.onclick = function(e) 
              {
                  s.style.backgroundColor = 'red';
                  alert(n);
              };
              return s;
          }

          function process(n) 
          {
              if(add)                    
                 s = makeSpan(n);
              else
                 s.parentNode.innerHTML = ""; //removing span by clearing the div innerHTML
              add = !add;
          }

          function loop() 
          {
              if (n < limit) 
              {
                  process(n);
                  n += 1;
                  setTimeout(loop, delay);
              }
          }

          loop();
      })(10000, 10);

   </script>
</body>