Использование замыканий JavaScript в setTimeout

Я использую 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. По некоторым причинам я не могу использовать "singleton" (например, window.renderer = новый рендерер ();) too

2 ответов


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

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

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

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

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

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

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

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

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


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

edit: этот ответ неверен. См. комментарии.