jQuery/Sizzle检查上下文内存泄漏

11
使用DevTools调试应用程序时,我发现“分离的DOM树”在累积。这些分离节点具有保留树,主要由checkContext函数组成(来自jQuery内部的sizzle-v1.10.1)。

Heap snapshot

我不确定该怎么继续下去。这个结果是什么意思?

2个回答

6
Sizzle将编译后的选择器存储在选择器缓存中,默认情况下最多存储50个条目。您可以在进行任何选择之前设置$.expr.cacheLength = 1来进行实验,看看它们是否消失。
这里是文档https://github.com/jquery/sizzle/wiki/Sizzle-Documentation#-internal-api。看起来是内部的,因此不要依赖它或实际生产代码中的任何内容。

谢谢!将缓存长度更改为1大大减少了分离节点的数量。这个功能对于标准网站可能很有用,但对于长时间运行的应用程序来说并没有意义。 - Konrad Dzwinel
1
我同意这不是泄漏——它受到控制。但是,在我们的应用程序中,这是一个问题——用户经常切换“模块”,而每个模块都包含大量的DOM节点。我们希望在下一个模块加载时尽快摆脱前一个模块。此外,我们不会多次调用相同的选择器——那将是令人尴尬的 :) - Konrad Dzwinel
@KonradDzwinel 我不是指在代码中写多次。我的意思是,在单页应用程序中,jQuery选择器缓存在页面更改之间保持不变,因此实际上很可能被访问到。而每个页面完全加载时都会清除缓存,并且所有选择器始终被完全计算。 - Esailija
2
每次打开模块时,它都会根据模板呈现并注入到DOM中。然后,我们启动一些选择器(主要是基于ID的)来捕获我们需要操作/监听的节点,并将这些节点缓存到变量中。每当用户更改模块时,我们希望完全删除先前的模块。使用Sizzle缓存会导致许多分离的节点留在内存中。因此,我认为在我们的应用程序中,Sizzle缓存对我们的帮助不大,反而有害。你同意吗? - Konrad Dzwinel
嗨@KonradDzwinel,我认为我和你有类似的流程结构,因为我使用了backbonejs和requirejs。这个解决方案到目前为止如何?你介意在这里分享吗? - Muhaimin
显示剩余3条评论

4

这实际上是一个bug,Sizzle没有必要保留上下文节点,它之所以这样做只是因为在设置临时变量后没有进行清理。我已经为此提交了问题,并进行了修复、运行了所有的Sizzle测试,并发起了一个pull request。

如果您想修补现有的jQuery或Sizzle副本:

  1. Open your jQuery or Sizzle file

  2. Search for the matcherFromTokens function

  3. Find this code within it (near the top):

    matchers = [ function( elem, context, xml ) {
        return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
            (checkContext = context).nodeType ?
                matchContext( elem, context, xml ) :
                matchAnyContext( elem, context, xml ) );
    } ];
    
  4. Change the return to var rv =, and add checkContext = undefined; and then return rv; at the end of the anonymous function, e.g.:

    matchers = [ function( elem, context, xml ) {
        var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
            (checkContext = context).nodeType ?
                matchContext( elem, context, xml ) :
                matchAnyContext( elem, context, xml ) );
        // Release the context node (issue #299)
        checkContext = null;
        return ret;
    } ];
    

注意: 这段代码将null赋值给checkContext,因为这是他们的风格。如果是我,我会改为赋值undefined

如果在合并过程中对修复程序提出任何问题,我将更新答案。

最好继续让Sizzle缓存选择器,因为jQuery使用编译的选择器与事件委托一起使用,并且不希望每次相关事件发生时都需要重新解析和重建匹配函数以确定元素是否与其匹配。


不幸的是,这不是jQuery保留元素的编译选择器的唯一位置。它所做的每个地方可能是一个可以修复的错误。我只有时间追踪到另一个地方,我也已经报告并修复了它(等待合并请求被接受):

如果搜索"Potentially complex pseudos",您会在: not伪选择器中找到此内容:

pseudos: {
    // Potentially complex pseudos
    "not": markFunction(function( selector ) {
        // Trim the selector passed to compile
        // to avoid treating leading and trailing
        // spaces as combinators
        var input = [],
            results = [],
            matcher = compile( selector.replace( rtrim, "$1" ) );

        return matcher[ expando ] ?
            markFunction(function( seed, matches, context, xml ) {
                var elem,
                    unmatched = matcher( seed, null, xml, [] ),
                    i = seed.length;

                // Match elements unmatched by `matcher`
                while ( i-- ) {
                    if ( (elem = unmatched[i]) ) {
                        seed[i] = !(matches[i] = elem);
                    }
                }
            }) :
            function( elem, context, xml ) {
                input[0] = elem;
                matcher( input, null, xml, results );
                return !results.pop();
            };
    }),

问题出在条件运算符中冒号后面的函数中::
function( elem, context, xml ) {
    input[0] = elem;
    matcher( input, null, xml, results );
    return !results.pop();
};

请注意,它从不清除input[0]。以下是修复方法:
function( elem, context, xml ) {
    input[0] = elem;
    matcher( input, null, xml, results );
    // Don't keep the element (issue #299)
    input[0] = null;
    return !results.pop();
};

目前我只有时间追踪到这些。


所以,你的意思是只有匹配函数应该被缓存?而不是整个结果?这就是为什么我没有报告它的原因,我认为Sizzle是有意缓存匹配的DOM节点。 - Konrad Dzwinel
@KonradDzwinel:没错。函数数组是需要可重用的部分。据我所知,checkContext 只是一个临时变量,用于在上面的匿名函数调用 matchContextmatchAnyContext 时给它们提供访问权限(我猜由于某种原因无法将其传递给它们)。匹配完成后没有必要继续持有上下文元素(而且完全不需要)。我真的很惊讶修复如此容易,虽然我认为我们必须等待并在以后清除它。但所有测试都通过了,所以我们将看看它是否经得起审查... - T.J. Crowder
这对于使用纯jQuery(jQlite不使用Sizzle)的SPA应用程序来说非常重要!做得好,谢谢! - Konrad Dzwinel
1
@KonradDzwinel: :-) 很高兴有机会为jQuery/Sizzle做出贡献。如果上述简单修复方法不通过,我将继续努力,我知道Sizzle团队会支持我们在不应该保留元素时不保留它们。 - T.J. Crowder

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