Использование замыканий 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: этот ответ неверен. См. комментарии.