Подтвердить что ты не робот

Рекурсивные замыкания в JavaScript

Скажем, у меня есть что-то вроде

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

animate(0);

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

На самом деле, вы не создаете там рекурсивную функцию. Вызывая 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. Но в любом случае.

Ответ 3

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

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

Ответ 4

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

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

function animate (param) {
    function doIt () {
        param++;
        if (param < 10) {
            setTimeout(doIt, 100);
        }
    };
    setTimeout(doIt, 100);
}

Ответ 5

Как насчет

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

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

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

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