2013-06-24 48 views
10

在DevTools中用'Profiles'調試我的應用程序時,我發現「分離的DOM樹」在積累。這些分離的節點具有主要由checkContext函數組成的保留樹(來自jQuery - v1.10.1中的sizzle)。jQuery/Sizzle checkContext內存泄漏

Heap snapshot

我不知道怎麼用這個進行。這個結果什麼意思?

回答

3

這實際上是一個bug,沒有理由Sizzle需要掛在上下文節點上,它只是做它,因爲它沒有設置臨時變量後清理。我提交了一個issue for it,修正了它,運行了所有的Sizzle測試,並做了一個pull請求。

如果要修補現有的jQuery或滋滋聲副本:

  1. 打開你的jQuery或嘶嘶聲文件

  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. 更改returnvar 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; 
    } ]; 
    

注意:該代碼分配nullcheckContext因爲很明顯這是他們的風格。如果是我,我將分配undefined

如果在請求/合併過程中發生的修復有問題,我會更新答案。

最好繼續讓Sizzle緩存選擇器,因爲jQuery使用事件委託編譯選擇器的東西,並且你不希望它每次發生相關事件時都必須重新分析和重建匹配器函數,所以它可以弄清楚元素是否匹配它。


不幸的是,這不是jQuery保存在已編譯選擇器中元素上的唯一地方。它所做的每個地方都可能是一個可以使用修復的bug。我只有時間追蹤另一個,我也報告並修復了這個問題(等待請求落地):

如果您搜索「潛在錯綜複雜」您會發現對於: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(); 
}; 

這就是我所有的時間來追查目前。

+0

所以,你說只有匹配器功能應該被緩存?不是全部結果?這就是爲什麼我沒有報告它,我認爲Sizzle故意緩存匹配的DOM節點。 –

+0

@KonradDzwinel:對。函數數組是需要重用的部分。據我所知,'checkContext'只是一個臨時變量,當上面的匿名函數調用它時,使用'matchContext'和'matchAnyContext'來訪問它(我猜它不能傳遞給它一些原因)。在匹配完成後(並且每個需要*不* *),都不需要保持上下文元素。不過,我確實很驚訝這個解決方案非常簡單,但我認爲我們必須等待並稍後清理它。但所有的測試都通過了,所以我們會看看它是否能夠繼續評論... –

+0

這對於使用純jQuery的SPA應用程序來說是巨大的(jQlite不使用Sizzle)!很棒的工作,謝謝你! –

5

Sizzle將編譯後的選擇器存儲在選擇器緩存中,缺省情況下最多可存儲50個條目。在做任何選擇之前,您可以通過設置$.expr.cacheLength = 1進行試驗,看看它們是否消失。

這是文檔https://github.com/jquery/sizzle/wiki/Sizzle-Documentation#-internal-api。似乎內部,所以不要依賴它或任何實際的生產代碼。

+0

謝謝!將緩存長度更改爲1會大大減少分離節點的數量。此功能可能對標準網站有用,但對於長時間運行的應用程序無效。 –

+0

@KonradDzwinel我認爲這恰恰相反 - 一個長時間運行的應用程序會一次又一次地使用相同的選擇器,從緩存中獲益非常大,而普通網站通常只運行一次選擇器。這不是內存泄漏。 – Esailija

+1

我同意這不是泄漏 - 它是受控制的。但是,對於我們的應用程序,這是一個問題 - 用戶經常切換「模塊」,每個模塊都包含大量的DOM節點。我們希望在下一個加載後立即擺脫先前的「模塊」。此外,我們不會多次調用相同的選擇器 - 這會令人尷尬:) –