Использование JavaScript-замыканий в setTimeout - PullRequest
9 голосов
/ 11 октября 2011

Я использую setTimeout для эмуляции рендеринга, и я пришел к такой структуре:

var Renderer = new Class (
{
    Implements: Events,

    initialize()
    {
        this.onRender();
    },

    onRender: function()
    {
        // some rendering actions
        setTimeout(this.onRender.bind(this), 20);
    }
});

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

function Renderer()
{
    var onRender = function()
    {
        // rendering
        setTimeout(onRender, 20);
    };
    onRender();
};

Но я не хочу терять Mootools Events и Classes.По некоторым причинам я не могу использовать "singleton" (например, window.renderer = new Renderer ();) тоже

Ответы [ 2 ]

18 голосов
/ 11 октября 2011

Ваш код в порядке, но ответ Энди вводит в заблуждение, потому что он путает цепочку областей действия с контекстом исполнения и, соответственно, расширением, стек вызовов .

Во-первых, setTimeout функции не не выполняются в глобальной области.Они по-прежнему выполняются в замыкании и могут обращаться к переменным из внешних областей.Это связано с тем, что JavaScript использует static scope ;то есть цепочка областей действия функции определяется в момент, когда функция создается и никогда не изменяется;цепочка областей действия является свойством функции.

контекст выполнения отличается и отличается от цепочки областей действия тем, что он создается во время вызова функции(напрямую - func(); - или в результате вызова браузера, такого как истечение времени ожидания).Контекст выполнения состоит из объекта активации (параметры функции и локальные переменные), ссылки на цепочку областей действия и значения this.

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

Если бы мы изменили вашу функцию onRender, чтобы она просто вызывала себя (this.onRender()), стек будет быстро переполнен.Это связано с тем, что элемент управления никогда не покидает каждую последующую функцию onRender, что позволяет вытолкнуть его контекст выполнения из стека вызовов.Вместо этого мы идем все глубже и глубже с каждым onRender, ожидающим возвращения следующего onRender, в бесконечном цикле, прерываемом только при переполнении стека.

Однако при вызове setTimeout управлениенемедленно возвращается и, таким образом, может выйти из функции onRender, в результате чего ее контекст выполнения будет вытолкнут из стека и отброшен (будет освобожден из памяти GC).

По истечении времени ожидания браузеринициирует вызов onRender из глобального контекста выполнения;стек вызовов всего два.Появился новый контекст выполнения, который по умолчанию наследовал бы глобальную область видимости как значение this;вот почему вы должны bind к вашему Renderer объекту - но он по-прежнему включает в себя исходную цепочку областей действия, которая была создана при первом определении onRender.

Как вы видите, вы не создаетебесконечные замыкания по рекурсии, потому что замыкания ( цепочки областей действия ) создаются при определении функции, а не при ее вызове.Кроме того, вы не создаете бесконечные контексты выполнения, потому что они отбрасываются после возврата onRender.

Мы можем убедиться, что вы не теряете память, протестировав ее .Я позволил этому бежать 500 000 раз и не наблюдал никакой утечки памяти.Обратите внимание, что максимальный размер стека вызовов составляет около 1000 (зависит от браузера) , поэтому он определенно не повторяется.

0 голосов
/ 11 октября 2011

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

изменить: этот ответ неверный. Смотрите комментарии.

...