【问题标题】:jQuery/Sizzle checkContext memory leakjQuery/Sizzle checkContext 内存泄漏
【发布时间】:2013-06-21 04:13:57
【问题描述】:

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

我不确定如何进行此操作。这个结果是什么意思?

【问题讨论】:

    标签: javascript jquery dom memory-leaks google-chrome-devtools


    【解决方案1】:

    Sizzle 将编译的选择器存储在选择器缓存中,默认情况下最多存储 50 个条目。您可以在进行任何选择之前通过设置$.expr.cacheLength = 1 进行试验,看看它们是否会消失。

    这是文档https://github.com/jquery/sizzle/wiki/Sizzle-Documentation#-internal-api。似乎是内部的,所以不要依赖它或实际生产代码中的任何东西。

    【讨论】:

    • 谢谢!将缓存长度更改为 1 大大减少了分离节点的数量。此功能可能对标准网站有用,但对长时间运行的应用没有意义。
    • @KonradDzwinel 我认为恰恰相反——长时间运行的应用程序会一遍又一遍地使用相同的选择器,这极大地受益于缓存,而普通网站通常只运行一次选择器。这不是内存泄漏。
    • 我同意这不是泄漏 - 它是可控的。但是,在我们的应用程序中,这是一个问题——用户经常切换“模块”,每个模块都包含许多 DOM 节点。我们希望在加载下一个“模块”后立即删除前一个“模块”。此外,我们不会多次调用相同的选择器——那会很尴尬:)
    • @KonradDzwinel 我的意思不是多次用代码编写。我的意思是,在单页应用程序中,jQuery 选择器缓存在页面更改之间持续存在,因此实际上很可能会被命中。而在每个页面上加载完整页面将清除缓存,并且所有选择器总是完全计算。
    • 每次打开模块时,它都会根据模板进行渲染并注入到 DOM 中。然后,我们启动一些选择器(主要基于 ID)来捕获我们需要操作/侦听的节点,并将它们自己缓存在变量中。每当用户更改模块时,我们都希望完全删除前一个模块。使用 sizzle 缓存时,许多分离的节点会留在内存中。因此,我认为对于我们的应用程序来说,嘶嘶声缓存的伤害大于它的帮助。你不同意吗?
    【解决方案2】:

    这实际上是一个错误,Sizzle 没有理由需要挂在上下文节点上,它只是这样做,因为它在设置临时变量后没有清理。我提交了issue for it,修复了它,运行了所有的 Sizzle 测试,并提出了拉取请求。

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

    1. 打开您的 jQuery 或 Sizzle 文件

    2. 搜索matcherFromTokens函数

    3. 在其中找到这段代码(靠近顶部):

      matchers = [ function( elem, context, xml ) {
          return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
              (checkContext = context).nodeType ?
                  matchContext( elem, context, xml ) :
                  matchAnyContext( elem, context, xml ) );
      } ];
      
    4. return改为var rv =,并在匿名函数末尾添加checkContext = undefined;return rv;,例如:

      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 节点。
    • @KonradDzwinel:对。函数数组是需要可重用的部分。据我所知,checkContext 只是一个临时变量,当上面的匿名函数调用它们时,matchContextmatchAnyContext 可以访问它(我猜它不能将它传递给他们一些原因)。匹配完成后无需保留上下文元素(并且每个都需要 not )。不过,我真的很惊讶修复如此简单,我认为我们必须等待很久才能将其清除。但是所有的测试都通过了,所以我们会看看它是否能通过审查......
    • 这对于使用纯 jQuery 的 SPA 应用程序来说是巨大的(jQlite 不使用 Sizzle)!干得好,谢谢!
    • @KonradDzwinel: :-) 很高兴有机会回馈 jQuery/Sizzle。如果上面的简单修复没有通过,我会坚持下去,我知道 Sizzle 团队会支持在我们不应该坚持的时候不坚持元素。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-25
    • 2013-04-02
    相关资源
    最近更新 更多