2013-06-24 8 views
10

Durante il debug della mia app con "Profili" in DevTools ho trovato "accumulato l'albero DOM" che si accumula. Questi nodi scollegati hanno un albero di contenimento composto principalmente dalle funzioni checkContext (provenienti da sizzle all'interno di jQuery - v1.10.1).jQuery/Sizzle checkContext perdita di memoria

Heap snapshot

Non sono sicuro di come procedere con questo. Cosa significa questo risultato?

risposta

3

Questo è in realtà un bug, non c'è motivo per cui Sizzle debba rimanere nel nodo di contesto, lo fa solo perché non si sta pulendo dopo aver impostato una variabile temporanea. Ho archiviato uno issue for it, risolto, eseguito tutti i test Sizzle e fatto una richiesta di pull.

Se si vuole correggere la vostra copia esistente di jQuery o Sizzle:

  1. Aprire il jQuery o Sizzle file di

  2. Cercare il matcherFromTokens funzione di

  3. Trova questo codice al suo interno (vicino alla cima):

    matchers = [ function(elem, context, xml) { 
        return (!leadingRelative && (xml || context !== outermostContext)) || (
         (checkContext = context).nodeType ? 
          matchContext(elem, context, xml) : 
          matchAnyContext(elem, context, xml)); 
    } ]; 
    
  4. Modificare il return-var rv =, e aggiungere checkContext = undefined; e poi return rv; alla fine della funzione anonima, ad es .:

    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; 
    } ]; 
    

Nota: Questo codice assegna null per checkContext perché a quanto pare questo è il loro stile. Se fossi in me, avrei assegnato lo undefined.

Se c'è qualche problema con la correzione sollevata durante il processo di richiesta/fusione, aggiornerò la risposta.

È meglio continuare a lasciare selettori di cache Sizzle, perché jQuery usa il selettore compilato con la delega degli eventi e non si vuole davvero che debba ripetere e ricostruire le funzioni di matcher ogni volta che si verifica un evento rilevante in modo che possa capire se gli elementi corrispondono.


Questo non è l'unico posto che jQuery conserva sugli elementi nei selettori compilati, sfortunatamente. Ogni posto che fa è probabilmente un bug che potrebbe usare il fixing.Ho solo avuto il tempo di rintracciare un altro, che ho anche segnalato e risolto (in attesa della richiesta di pull essere atterrato):

Se si cerca "pseudos potenzialmente complesse" troverete questo per la :not pseudo-selettore:

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(); 
      }; 
    }), 

il problema è in funzione, dopo l': nell'operatore condizionale:

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

noti che non cancella input[0]. Ecco la soluzione:

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(); 
}; 

Questo è tutto quello che ho il tempo di rintracciare al momento.

+0

Quindi, stai dicendo che solo la funzione matcher doveva essere memorizzata nella cache? Non l'intero risultato? Questo è il motivo per cui non l'ho segnalato, ho pensato che Sizzle stia memorizzando nella cache i nodi DOM di proposito. –

+0

@KonradDzwinel: Giusto. L'array di funzioni è la parte che deve essere riusabile. Per quanto ne so, 'checkContext' è solo una variabile temporanea, usata in modo che' matchContext' e 'matchAnyContext' abbiano accesso ad essa quando la funzione anonima sopra li chiama (suppongo che non possa passarli a loro per alcuni ragionare). Non è necessario mantenere l'elemento di contesto dopo che la corrispondenza è stata eseguita (e ogni esigenza * non * a). Ero davvero sorpreso che la soluzione fosse così semplice, pensavo che avremmo dovuto aspettare e chiarirlo molto dopo. Ma tutti i test passano, quindi vedremo se sopravviverà alla revisione ... –

+0

Questo è enorme per le app SPA che usano jQuery puro (jQlite non usa Sizzle)! Ottimo lavoro e grazie! –

5

Sizzle memorizza i selettori nella cache del selettore, che per impostazione predefinita memorizza fino a 50 voci. Potresti sperimentare impostando $.expr.cacheLength = 1 prima di fare qualsiasi selezione e vedere se se ne vanno.

Ecco i documenti https://github.com/jquery/sizzle/wiki/Sizzle-Documentation#-internal-api. Sembra interno quindi non dipendono da esso o altro nel codice di produzione attuale.

+0

Grazie! Modifica della lunghezza della cache a 1 numero ridotto di nodi scollegati notevolmente. Questa funzione può essere utile per i siti web standard, ma non ha senso per le app da lungo tempo. –

+0

@KonradDzwinel Penso che sia esattamente l'opposto: un'app funzionante a lungo utilizzerà sempre gli stessi selettori che beneficiano enormemente della memorizzazione nella cache, mentre un normale sito Web esegue in genere i suoi selettori una sola volta. Non è una perdita di memoria. – Esailija

+1

Sono d'accordo che questa non è una perdita: è controllata. Tuttavia, nel caso della nostra app è un problema: gli utenti spesso cambiano i "moduli" e ogni modulo è composto da molti nodi DOM. Vogliamo sbarazzarci di un precedente 'modulo' non appena viene caricato il prossimo. Inoltre, non chiamiamo gli stessi selettori più volte - sarebbe imbarazzante :) –