jQuery/Sizzle checkContext内存泄漏

Kon*_*nel 10 javascript jquery dom memory-leaks google-chrome-devtools

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

堆快照

我不知道如何处理这个问题.这个结果什么意思?

Esa*_*ija 5

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

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

  • 每次打开模块时,它都会根据模板呈现并注入DOM.然后,我们启动一些选择器(主要是基于ID)来捕获我们需要操作/监听的节点,并将它们自己缓存在变量中.每当用户更改模块时,我们都想完全删除前一个模块.随着sizzle缓存,许多分离的节点留在内存中.因此,我认为,如果我们的应用程序 - sizzle缓存伤害超过它有帮助.你不同意吗? (2认同)

T.J*_*der 4

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

如果您想修补现有的 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 ) );
    } ];
    
    Run Code Online (Sandbox Code Playgroud)
  4. return将改为var rv =,并在匿名函数末尾添加checkContext = undefined;和 then ,例如: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;
    } ];
    
    Run Code Online (Sandbox Code Playgroud)

注意:该代码分配nullcheckContext因为显然这是他们的风格。如果是我,我就会分配undefined

如果在拉取请求/合并过程中提出的修复有任何问题,我将更新答案。

最好继续让 Sizzle 缓存选择器,因为 jQuery 使用带有事件委托的已编译选择器内容,并且您并不真正希望它在每次发生相关事件时都必须重新解析和重建匹配器函数,以便它可以确定元素是否匹配它。


不幸的是,这并不是 jQuery 在编译选择器中保留元素的唯一地方。它所做的每个地方都可能是一个需要修复的错误。我只有时间去追踪另一个问题,我也报告并修复了这个问题(等待拉取请求落地):

如果您搜索“潜在复杂的伪”,您会发现: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();
            };
    }),
Run Code Online (Sandbox Code Playgroud)

问题出在:条件运算符之后的函数中:

function( elem, context, xml ) {
    input[0] = elem;
    matcher( input, null, xml, results );
    return !results.pop();
};
Run Code Online (Sandbox Code Playgroud)

请注意,它永远不会清除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();
};
Run Code Online (Sandbox Code Playgroud)

这就是我目前有时间追踪的全部内容。