Рекурсивные замыкания в JavaScript - PullRequest
36 голосов
/ 11 августа 2011

Допустим, у меня есть что-то вроде

function animate(param)
{
    // ...
    if (param < 10)
        setTimeout(function () { animate(param + 1) }, 100);
}

animate(0);

Означает ли это, что каждый экземпляр локальных данных функции будет храниться в памяти до завершения анимации, то есть до тех пор, пока параметр не достигнет 10?Это правда, что экземпляры хранятся в памяти, есть ли лучший способ сделать это?Я знаю, что передача текстового кода в setTimeout() решает проблему, но в моем случае среди аргументов функции есть объекты, которые нельзя легко представить в виде строк.

Ответы [ 5 ]

13 голосов
/ 11 августа 2011

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

  1. animate(0) называется.
  2. Создается замыкание с param == 0, теперь оно предотвращает освобождение этой переменной.
  3. Время ожидания срабатывает, animate(1) вызывается.
  4. Создано новое замыкание с param == 1, теперь оно препятствует освобождению этой переменной.
  5. Первое закрытие завершается, к этому моменту оно больше не ссылается и может быть освобождено. Локальные переменные из первого вызова animate() также могут быть освобождены.
  6. Повторите, начиная с шага 3, теперь с animate(2).
6 голосов
/ 11 августа 2011

На самом деле, вы не создаете там рекурсивную функцию.Вызывая setTimeout, он больше не называет себя.Единственное замыкание, созданное здесь, - это анонимная функция для setTimeout, после выполнения которой сборщик мусора распознает, что ссылка на предыдущий экземпляр может быть очищена.Это, вероятно, не произойдет мгновенно, но вы определенно не можете создать stack overflow, используя это.Давайте рассмотрим этот пример:

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   setTimeout(myFunc, 200);
}
myFunc();

Наблюдение за использованием памяти в вашем браузере.Он будет постоянно расти, но через некоторое время (20-40 секунд для меня) он снова будет полностью очищен.С другой стороны, если мы создадим реальную рекурсию, подобную этой:

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   myFunc();
}
myFunc();

Наше использование памяти будет расти, браузеры заблокируются, и мы наконец создадим эту stack overflow.Javascript не реализует рекурсию хвоста , поэтому мы получим переполнение во всех случаях.


обновление

похоже, я ошибался в своем первом примере.Такое поведение верно только в том случае, если не вызывается контекст-функция (например, с использованием анонимной функции).Если мы переписываем это как

function myFunc() {
   var bigarray = new Array(10000).join('foobar');

   setTimeout(function() {
     myFunc();
   }, 200);
}
myFunc();

Память браузера больше не освобождается.Растет вечно.Это может быть из-за того, что любая внутренняя закрытость сохраняет ссылку на bigarray.Но все равно.

4 голосов
/ 11 августа 2011

Будет сохранен только контекст, связанный с самым последним вызовом animate (теоретически все зависит от сборщика мусора).Когда animate создает анонимную функцию, анонимная функция получает ссылку на контекст этого вызова на animate, поэтому этот контекст остается в памяти.По истечении времени ожидания ссылка на код таймера на анонимную функцию освобождается, что освобождает ссылку этой функции на контекст.Тем временем был создан новый контекст, но новый контекст не ссылается на старый, поэтому он не будет хранить старый в памяти.

Ключом к этому является то, что привязка контекста кфункция происходит, когда функция создана , а не когда она вызывается.

2 голосов
/ 11 августа 2011

Да, в вашем примере, каждый раз, когда выполняется функция animate, создается новая функция со своей собственной областью закрытия.

Чтобы избежать множественных замыканий, вы можете попробовать что-то вроде этого:

function animate (param) {
    function doIt () {
        param++;
        if (param < 10) {
            setTimeout(doIt, 100);
        }
    };
    setTimeout(doIt, 100);
}
0 голосов
/ 11 августа 2011

Как насчет

function animate(param)
{
  //....
  if(param < 10)
    animateTimeout(param);
}

function animateTimeout(param)
{
   setTimout(function() { animate(param + 1) }, 100 );
}

Таким образом, вы не сохраняете локальные данные в // ... во время ожидания.

Не уверен, если вы думаете, что здесь больше проблем, чем на самом деле. Ваш код не является рекурсивным в том смысле, что он приведет к 10 глубоким цепочкам замыканий, потому что замыкание 1 завершается после второго вызова animate. Каждое замыкание существует только на время существования одного setTimeout.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...