在setTimeout中使用JavaScript闭包 [英] Using JavaScript closures in setTimeout

查看:212
本文介绍了在setTimeout中使用JavaScript闭包的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用setTimeout来模拟渲染,我来到这样的结构:

  var Renderer = new Class (
{
)实现:事件,

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

onRender:function()
{
//一些渲染操作
setTimeout(this.onRender.bind(this),20);
}
});

这个代码是否有潜在的内存泄漏,因为闭包的无限嵌套?或一切都好吗?我到目前为止唯一的解决方案是重写它到通常

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

但我不想失去Mootools事件和类。
由于某些原因,我不能使用singleton(如window.renderer = new Renderer();)。

解决方案

您的代码很好,但Andy的回答是误导,因为它混淆了 范围链 执行上下文



首先, setTimeout 函数不要在全局范围中执行。他们仍然在闭包中执行,并且可以从外部范围访问变量。这是因为JavaScript使用静态作用域;也就是说,函数的 scope链 时定义,永不改变;

范围链不同, 它是在调用函数时构造的(无论直接使用– func(); –还是作为浏览器调用的结果,例如超时到期) 。执行上下文由激活对象(函数的参数和局部变量),对范围链的引用以及 this 的值组成。



调用堆栈可以被认为是一个执行上下文的数组。堆栈的底部是全局执行上下文。每次调用函数时,其参数和 this 值都存储在堆栈中的新对象中。



如果我们改变你的 onRender 函数来简单地调用自己( this.onRender()会很快溢出。这是因为控制永远不会离开每个连续的 onRender 函数,允许它的执行上下文从调用栈中弹出。相反,我们进行更深更深的每个 onRender 等待下一个 onRender 返回,在无限循环中当堆栈溢出时。



但是,通过调用 setTimeout ,控制立即返回, onRender 函数,使其执行上下文从堆栈中弹出并丢弃(由GC释放内存)。



当超时过期时,浏览器从全局执行上下文启动对 onRender 的调用;调用栈只有两个深。有一个新的执行上下文–它默认会继承全局作用域作为 this 值;这就是为什么你必须 bind 到你的 Renderer object–但它仍然包括在您首次定义 onRender 时创建的原始范围链。



你不是通过递归创建无限闭包,因为闭包( scope链)是在函数定义而不是函数调用中创建的。此外,您不会创建无限执行上下文,因为它们会在 onRender 返回后丢弃。



你不会通过测试泄漏记忆。我让它运行50万次,没有观察到任何泄​​漏的记忆。请注意,最大调用堆栈大小约为1,000(根据浏览器而异),因此绝对不会递归。


I'm using setTimeout to emulate rendering, and I came to the structure like this:

var Renderer = new Class (
{
    Implements: Events,

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

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

Does that code have potential memory leaks because of infinite nesting of closures? Or everything is ok? The only solution I came so far is to rewrite it to usual

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

But I don't want to lose Mootools Events and Classes. For some reasons I can't use a "singleton" (like window.renderer = new Renderer();) too

解决方案

Your code is fine, but Andy's answer is misleading because it confuses the scope chain with execution context and, by extension, call stack.

First, setTimeout functions do not execute in the global scope. They still execute in a closure and can access variables from outer scopes. This is because JavaScript uses static scope; that is, the scope chain of a function is defined at the moment that function is created and never changes; the scope chain is a property of the function.

Execution context is different and separate from the scope chain in that it is constructed at the time a function is invoked (whether directly – func(); – or as the result of a browser invocation, such as a timeout expiring). The execution context is composed of the activation object (the function's parameters and local variables), a reference to the scope chain, and the value of this.

The call stack can be thought of as an array of execution contexts. At the bottom of the stack is the global execution context. Each time a function is called, its parameters and this value are stored in a new 'object' on the stack.

If we were to change your onRender function to simply call itself (this.onRender()), the stack would quickly overflow. This is because control would never leave each successive onRender function, allowing its execution context to be popped off the call stack. Instead, we go deeper and deeper with each onRender waiting for the next onRender to return, in an infinite cycle broken only when the stack overflows.

However, with a call to setTimeout, control returns immediately and thus is able to leave the onRender function, causing its execution context to be popped off the stack and discarded (to be freed from memory by the GC).

When the timeout expires, the browser initiates a call to onRender from the global execution context; the call stack is only two deep. There is a new execution context – which by default would inherit the global scope as its this value; that's why you have to bind to your Renderer object – but it still includes the original scope chain that was created when you first defined onRender.

As you can see, you're not creating infinite closures by recursion because closures (scope chains) are created at function definition, not at function invocation. Furthermore, you're not creating infinite execution contexts because they are discarded after onRender returns.

We can make sure you're not leaking memory by testing it. I let it run 500,000 times and didn't observe any leaking memory. Note that the maximum call stack size is around 1,000 (varies by browser), so it's definitely not recursing.

这篇关于在setTimeout中使用JavaScript闭包的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆