如何增加Javascript中的最大调用栈?

12

我有一个可以自行触发的事件。我试图尽可能地使代码高效,但在某些情况下,它可能会触及最大调用堆栈,而这些情况是我无法控制的。它不是无限堆栈,它最终会结束,但有时由于限制,它在完成之前可能潜在崩溃。

如果我设置了2个类似的事件监听器并分割代码,是否会增加调用堆栈的数量?还是我应该做些什么?

更新:这是在DOM更改事件上(只适用于Webkit,所以不用担心其他浏览器),该事件也可以基于某些条件修改DOM。我尚未真正达到那个限制,但从理论上讲,它有可能。我仍在优化代码,尽可能减少DOM操作。

更新2:我包含了一个示例(非真实)示例:

document.addEventListener('DOMSubtreeModified', function(event){
            
    this.applyPolicy(event);
            
}, true);

function applyPolicy(event){
    if( typeof event != "undefined" ){
        event.stopPropagation();
        event.stopImmediatePropagation();
    }
    
    if( !isButtonAllowed ){
        $('button:not(:disabled)').each(function(){
            
           $(this).attr('disabled', true);

        });
    }
}

这只是一个示例代码,但即使在这种情况下,如果你有100个按钮,调用堆栈也会有100个。请注意,如果你使用$('button').attr('disabled', true);,这会导致调用堆栈问题,因为jQuery将无限尝试修改DOM。


1
也许你应该将递归函数转换成“while”循环?我无法想象你需要实际触发数十万个事件来触发其他函数的副作用... - user1207456
1
从未达到过这个限制,除非编写了一个无限循环的错误代码。请展示代码。 - gpasci
2
简短回答:目前没有(标准)机制可以实现这一点。唯一的选择是修改代码。 - user166390
没有“标准”的做法,但如果你只是用它来调试,那么是否有可能做到呢? - agentofuser
我正在尝试查找DOM中具有特定值的属性的对象路径,即使我已经处理了循环引用,但仍然达到了调用堆栈限制。如果能够增加它,那将非常有用。更多信息请参见http://stackoverflow.com/questions/13551321/how-do-i-find-the-full-path-to-an-object-that-has-a-property-with-a-given-value。 - agentofuser
显示剩余5条评论
6个回答

7

虽然听起来你可能需要重新考虑一些代码,但一个可能的解决方案是在给定的时间间隔内使用setTimeout进行递归调用。这样可以开始一个新的调用栈。

以这个例子为例...

var i = 0;

function start() {
    ++i;
    var is_thousand = !(i % 1000);

    if (is_thousand)
        console.log(i);

    if (i >= 100000)
        return; // safety halt at 100,000
    else
        start()
}

它只是在每个1,000的间隔上记录到控制台。在Chrome中,它超过了30,000范围内的堆栈。

演示: http://jsfiddle.net/X44rk/


但如果你像这样重新制作它...
var i = 0;

function start() {
    ++i;
    var is_thousand = !(i % 1000);

    if (is_thousand)
        console.log(i);

    if (i >= 100000) // safety halt at 100,000
        return;
    else if (is_thousand)
        setTimeout(start, 0);
    else
        start();
}

现在,每到1,000次,该函数就会被允许返回,并开始新的异步调用堆栈。需要注意的是,这假设递归调用时函数已经有效地结束。此外,我有一个条件,在100,000次时停止,以避免无限循环。DEMO: http://jsfiddle.net/X44rk/1/

这看起来很有趣。我会试一试。谢谢! - Sherzod
@shershams:不用谢。如果你有问题,请告诉我。 - user1106925
1
我尝试了您的代码进行了百万次递归,它仍然可以正常工作。我正在使用这种技术来实现我的代码。谢谢! - Sherzod

5

对于任何浏览器来说,最大调用栈运行良好的值通常在数千之内。您应该尝试优化代码,因为巨大的调用栈不利于速度和内存。

如果您遇到这个问题,那么这表明您的代码迫切需要重新组织。


问题在于,该事件正在监听DOM更改,并在检查一些条件后,实际上可以修改DOM,这将再次触发该事件。因此,调用堆栈可能会非常快地增加。我仍在努力优化代码,尽可能减少DOM操作。 - Sherzod
我正在使用JavascriptMVC和jQuery。在这种情况下减少递归的唯一方法是减少DOM操作的数量,以便它不会如此频繁地触发自身...有更好的方法吗? - Sherzod
将事件添加到DOM操作本身就是一件不好的事情。在理想情况下,更新DOM的代码应该同时触发一个内部事件。不要让你的事件通过DOM流动,它应该是单向的。 - Evert
1
如果JavascriptMVC鼓励这种模式,我会对这个特定的框架产生严重的怀疑。(作为一个曾经就这个问题做过公开演讲的人说) - Evert
2
如果你真的在处理第三方,那么我同意你的看法。这可能是处理这种情况的唯一方法。在这种情况下,我会尝试确保您的事件不会递归触发。当您的第一个DOM更改事件触发时,您可以暂时禁用此事件,并且只有在完成此事件后才重新启用它。这将使堆栈无法失控增长。附言:我喜欢在stackoverflow上讨论堆栈溢出问题 ;) - Evert
显示剩余5条评论

0

调用堆栈的大小和实现细节将取决于您的代码运行的JavaScript环境。即使您可以操纵堆栈使用情况以在您现在关心的环境中运行,但在提供不同堆栈大小的环境中运行时,堆栈占用过多的代码很可能会崩溃。

任何递归程序都可以重写为迭代程序。请考虑在您的情况下是否可以这样做。请参见:

从递归到迭代的方法


0

不能这样做,它们依赖于浏览器,并且它们的范围相当广泛,所以我认为不需要担心。


0

如果您遇到调用栈限制,则几乎可以确定存在递归事件序列。事件依赖堆栈,因此它们并不是实现循环的最佳方式。在没有尾部调用消除的语言中,您唯一的选择是使用标准循环结构,如for/in。


0

在发现您正在审查可能渲染您不想呈现的元素的第三方代码后,我了解到真正的根本问题:黑名单在不受信任的代码上永远不起作用,最终,某些代码将通过您的黑名单,您就做完了。

除了重新设计您的代码以使没有第三方代码期望(或被给予)访问DOM之外,我的解决方案如下:

  1. 将您的DOM复制到一个隐藏的iframe中。
  2. 在该iframe中对第三方代码进行沙箱处理。
  3. 在iframe中的任何DOM更改时,检查两个DOM树之间的差异。如果更改通过了白名单,则将其提取到您的实际DOM中(重新连接事件回调等)。
  4. 在更新后将“实际” DOM的结构复制到iframe DOM中,在此过程中清除其中的任何敏感数据。

您仍然需要在iframe中检查DOM事件,但是您不会在“实际”页面中检查,因此您不会进入无限循环。

这是基于一种假设,即您确实无法信任第三方代码执行其所应该执行的操作。如果只是供应商的不称职,可以忽略净化部分,但仍然使用白名单而不是黑名单。


代码本身在发布之前会被另一个脚本进行分析,但第三方开发者可能会忘记在某些事件上禁用某些东西,因此我需要检查并执行该策略。我一定会尝试使用你提到的方法创建一些非常简单的东西。Iframes 的想法听起来很有趣,尽管我讨厌它们。谢谢! - Sherzod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接