Использование 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 Events и Classes. По некоторым причинам я не могу использовать "синглтон" (например, window.renderer = new Renderer();) тоже

2 ответа

Решение

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам