Häufige JavaScript-Speicherfehler und Lösungen

Häufige JavaScript-Speicherfehler und Lösungen

Vorwort:

JavaScript bietet keine Speicherverwaltungsoperationen. Stattdessen wird der Speicher von JavaScript VM durch einen Speicherrückgewinnungsprozess verwaltet, der als Garbage Collection bezeichnet wird.

Da wir die Garbage Collection nicht erzwingen können, woher wissen wir, dass sie funktioniert? Und was wissen wir darüber?

Die Skriptausführung wird während dieses Vorgangs angehalten. Es gibt Speicher für nicht zugängliche Ressourcen frei. Es ist nicht deterministisch. Es überprüft nicht den gesamten Speicher auf einmal, sondern wird in mehreren Zyklen ausgeführt. Es ist unvorhersehbar, wird aber bei Bedarf ausgeführt. Bedeutet dies, dass Sie sich keine Gedanken über Probleme bei der Ressourcen- und Speicherzuweisung machen müssen? Natürlich nicht. Wenn wir nicht aufpassen, kann es zu Speicherlecks kommen.

Was ist ein Speicherleck?

Ein Speicherleck ist ein Block zugewiesenen Speichers, den die Software nicht zurückfordern kann.

Javascript bietet einen Garbage Collector, aber das bedeutet nicht, dass wir Speicherlecks vermeiden können. Um für die Garbage Collection in Frage zu kommen, darf auf das Objekt nicht von woanders aus verwiesen werden. Wenn Verweise auf nicht verwendete Ressourcen gespeichert werden, wird die Erfassung dieser Ressourcen verhindert. Dies wird als unbewusste Gedächtnisspeicherung bezeichnet.

Speicherverlust kann dazu führen, dass der Garbage Collector häufiger ausgeführt wird. Da dieser Vorgang die Ausführung des Skripts verhindert, kann es dazu führen, dass unser Programm hängen bleibt. Wenn es hängen bleibt, werden wählerische Benutzer es definitiv bemerken, und wenn sie damit unzufrieden sind, wird das Produkt bald offline sein. In schwerwiegenderen Fällen kann die gesamte Anwendung abstürzen, und dann wird es gg.

Wie können Speicherlecks verhindert werden? Die Hauptsache ist, dass wir vermeiden sollten, unnötige Ressourcen beizubehalten. Sehen wir uns einige gängige Szenarien an.

1. Zeitüberwachung

setInterval() ruft eine Funktion auf oder führt einen Codeausschnitt wiederholt mit einer festen Zeitverzögerung zwischen jedem Aufruf aus. Es gibt eine Intervall-ID zurück, die das Intervall eindeutig identifiziert, sodass Sie es später durch Aufrufen clearInterval() entfernen können.

Wir erstellen eine Komponente, die eine Rückruffunktion aufruft, um zu signalisieren, dass sie nach x Schleifen fertig ist. In diesem Beispiel verwende ich React , aber dies gilt für jedes FE-Framework.

importiere React, { useRef } von „react“; 
 
const Timer = ({ Zyklen, beim Ende }) => { 
    const currentCicles = useRef(0); 
 
    setzeIntervall(() => { 
        wenn (aktuelleZyklen.aktuelle >= Zyklen) { 
            beimFertigstellen(); 
            zurückkehren; 
        } 
        aktuelleZyklen.current++; 
    }, 500); 
 
    zurückkehren ( 
        <div>Wird geladen ...</div> 
    ); 
} 
 
Standard-Timer exportieren; 


Auf den ersten Blick scheint es kein Problem zu geben. Keine Sorge, erstellen wir eine weitere Komponente, die diesen Timer auslöst, und analysieren ihre Speicherleistung.

importiere React, { useState } von 'react'; 
Stile aus „../styles/Home.module.css“ importieren 
Timer aus „../Komponenten/Timer“ importieren; 
 
Exportieren der Standardfunktion Home() { 
    const [showTimer, setShowTimer] = useState(); 
    const onFinish = () => setShowTimer(false); 
 
    zurückkehren ( 
      <div Klassenname={styles.container}> 
          {showTimer ? ( 
              <Timer cicles={10} onFinish={onFinish} /> 
          ): ( 
              <button onClick={() => setzeShowTimer(true)}> 
                Wiederholen 
              </button> 
          )} 
      </div> 
    ) 
} 


Nach einigen Klicks auf die Schaltfläche „Wiederholen“ wird dies das Ergebnis der Abfrage der Speichernutzung mit Chrome Dev Tools angezeigt:

Wenn wir auf die Schaltfläche „Wiederholen“ klicken, können wir sehen, dass immer mehr Speicher zugewiesen wird. Dies bedeutet, dass der zuvor zugewiesene Speicher nicht freigegeben wurde. Der Timer läuft weiterhin, anstatt ersetzt zu werden.

Wie lässt sich dieses Problem lösen? Der Rückgabewert von setInterval ist eine Intervall-ID, die wir zum Abbrechen des Intervalls verwenden können. In diesem speziellen Fall können wir clearInterval aufrufen, nachdem die Komponente ausgehängt wurde.

useEffect(() => { 
    const IntervallId = setInterval(() => { 
        wenn (aktuelleZyklen.aktuelle >= Zyklen) { 
            beimFertigstellen(); 
            zurückkehren; 
        } 
        aktuelleZyklen.current++; 
    }, 500); 
 
    return () => Intervall löschen(Intervall-ID); 
}, []) 


Manchmal ist es schwierig, dieses Problem beim Schreiben von Code zu finden. Der beste Weg besteht darin, die Komponente zu abstrahieren.

Wenn wir hier React verwenden, können wir die gesamte Logik in einen benutzerdefinierten Hook verpacken.

importiere { useEffect } von 'react'; 
 
export const useTimeout = (refreshCycle = 100, Rückruf) => { 
    useEffect(() => { 
        wenn (Aktualisierungszyklus <= 0) { 
            setTimeout(Rückruf, 0); 
            zurückkehren; 
        } 
 
        const IntervallId = setInterval(() => { 
            Rückruf(); 
        }, Aktualisierungszyklus); 
 
        return () => Intervall löschen(Intervall-ID); 
    }, [Aktualisierungszyklus, Intervall festlegen, Intervall löschen]); 
}; 
 
Standard-UseTimeout exportieren; 


Wenn Sie nun „setInterval“ verwenden müssen, können Sie Folgendes tun:

const handleTimeout = () => ...; 
 
Zeitüberschreitung verwenden (100, Zeitüberschreitung handhaben); 


Jetzt können Sie diesen useTimeout Hook verwenden, ohne sich um Speicherlecks sorgen zu müssen, was ebenfalls ein Vorteil der Abstraktion ist.

2. Ereignisüberwachung

Web API bietet eine große Anzahl von Ereignis-Listenern. Zuvor haben wir setTimeout besprochen. Schauen wir uns nun addEventListener an.

In diesem Beispiel erstellen wir eine Tastenkombinationsfunktion. Da wir auf verschiedenen Seiten unterschiedliche Funktionen haben, erstellen wir unterschiedliche Tastenkombinationsfunktionen

Funktion homeShortcuts({ Schlüssel}) { 
    wenn (Schlüssel === 'E') { 
        console.log('Widget bearbeiten') 
    } 
} 
 
// Der Benutzer meldet sich auf der Homepage an, wir führen document.addEventListener('keyup', homeShortcuts); aus.  
 
 
// Der Benutzer führt eine Aktion aus und navigiert dann zur Einstellungsfunktion settingsShortcuts({ key}) { 
    wenn (Schlüssel === 'E') { 
        console.log('Einstellung bearbeiten') 
    } 
} 
 
// Der Benutzer meldet sich auf der Startseite an, wir führen document.addEventListener('keyup', settingsShortcuts); aus.  


Es sieht immer noch gut aus, außer dass die vorherige keyup nicht bereinigt wird, wenn der zweite addEventListener ausgeführt wird. Anstatt unseren keyup Listener zu ersetzen, fügt dieser Code einen weiteren callback hinzu. Das heißt, wenn eine Taste gedrückt wird, werden zwei Funktionen ausgelöst.

Um den vorherigen Rückruf zu löschen, müssen wir removeEventListener verwenden:

document.removeEventListener('keyup', homeShortcuts); 


Refaktorisieren Sie den obigen Code:

Funktion homeShortcuts({ Schlüssel}) { 
    wenn (Schlüssel === 'E') { 
        console.log('Widget bearbeiten') 
    } 
} 
 
// Benutzer landet auf Home und wir führen aus 
document.addEventListener('keyup', homeShortcuts);  
 
 
// Der Benutzer erledigt einige Aufgaben und navigiert zu den Einstellungen 
 
FunktionseinstellungenShortcuts({ key}) { 
    wenn (Schlüssel === 'E') { 
        console.log('Einstellung bearbeiten') 
    } 
} 
 
// Benutzer landet auf Home und wir führen aus 
document.removeEventListener('keyup', homeShortcuts);  
document.addEventListener('keyup', EinstellungenShortcuts); 


Als Faustregel gilt: Seien Sie sehr vorsichtig, wenn Sie Tools aus dem globalen Objekt verwenden.

3.Beobachter

Observers ist eine Funktion der Browser- Web API , die vielen Entwicklern nicht bekannt ist. Dies ist leistungsstark, wenn Sie Änderungen hinsichtlich der Sichtbarkeit oder Größe von HTML-Elementen überprüfen möchten.

Die Schnittstelle IntersectionObserver (untergeordnet der API „Intersection Observer“) bietet eine Möglichkeit, den Schnittpunktzustand eines Zielelements mit seinen Vorgängerelementen oder dem Dokumentfenster der obersten Ebene (Ansichtsfenster) asynchron zu beobachten. Das Vorgängerelement und der Ansichtsbereich werden als Wurzeln bezeichnet.

Obwohl es leistungsstark ist, müssen wir es mit Vorsicht einsetzen. Wenn Sie mit der Beobachtung eines Objekts fertig sind, denken Sie daran, es abzubrechen, wenn es nicht verwendet wird.

Schauen Sie sich den Code an:

Konstante Ref = ... 
const sichtbar = (sichtbar) => { 
  console.log(`Es ist ${visible}`); 
} 
 
useEffect(() => { 
    wenn (!ref) { 
        zurückkehren; 
    } 
 
    Beobachter.aktuell = neuer IntersectionObserver( 
        (Einträge) => { 
            wenn (!entries[0].isIntersecting) { 
                sichtbar (wahr); 
            } anders { 
                sichtbar (falsch); 
            } 
        }, 
        { rootMargin: `-${header.height}px` }, 
    ); 
 
    Beobachter.aktuell.beobachten(ref); 
}, [ref]); 


Der obige Code sieht gut aus. Was passiert jedoch mit dem Beobachter, wenn die Komponente ausgehängt wird? Er wird nicht gelöscht und es kommt zu einem Speicherverlust.

Wie lösen wir dieses Problem? Verwenden Sie einfach die Disconnect-Methode:

Konstante Ref = ... 
const sichtbar = (sichtbar) => { 
  console.log(`Es ist ${visible}`); 
} 
 
useEffect(() => { 
    wenn (!ref) { 
        zurückkehren; 
    } 
 
    Beobachter.aktuell = neuer IntersectionObserver( 
        (Einträge) => { 
            wenn (!entries[0].isIntersecting) { 
                sichtbar (wahr); 
            } anders { 
                sichtbar (falsch); 
            } 
        }, 
        { rootMargin: `-${header.height}px` }, 
    ); 
 
    Beobachter.aktuell.beobachten(ref); 
 
    return () => Beobachter.aktuell?.trennen(); 
}, [ref]); 


4. Fensterobjekt

Das Hinzufügen von Objekten zu Window ist ein häufiger Fehler. In einigen Szenarien kann es schwierig sein, es zu finden, insbesondere wenn das Schlüsselwort this im Kontext Window Execution verwendet wird.

Schauen Sie sich das folgende Beispiel an:

Funktion addElement(Element) { 
    wenn (!dieser.stapel) { 
        dieser.Stapel = { 
            Elemente: [] 
        } 
    } 
 
    dies.stack.elements.push(element); 
} 


Es sieht harmlos aus, aber es hängt davon ab, aus welchem ​​Kontext Sie addElement aufrufen. Wenn Sie addElement aus Window Context aufrufen, wird der Stapel größer.

Ein weiteres Problem könnte die falsche Definition einer globalen Variable sein:

var a = 'Beispiel 1'; // Bereich, in dem var erstellt wird b = 'Beispiel 2'; // zum Fensterobjekt hinzugefügt

Um dieses Problem zu vermeiden, können Sie den strikten Modus verwenden:

"streng verwenden" 


Durch die Verwendung des strikten Modus signalisieren Sie dem JavaScript Compiler, dass Sie sich vor diesem Verhalten schützen möchten. Sie können Window weiterhin verwenden, wenn Sie es benötigen. Sie müssen es jedoch explizit verwenden.

Wie sich der strikte Modus auf unser vorheriges Beispiel auswirkt:

  • Für die Funktion addElement ist dies nicht definiert, wenn sie aus dem globalen Bereich aufgerufen wird.
  • Wenn Sie für eine Variable nicht const | let | var angeben, erhalten Sie die folgende Fehlermeldung:
Nicht erfasster Referenzfehler: b ist nicht definiert 


5. Halten Sie die DOM-Referenz

Auch DOM-Knoten sind nicht immun gegen Speicherlecks. Wir müssen darauf achten, keine Verweise auf sie zu speichern. Andernfalls kann der Garbage Collector sie nicht bereinigen, da sie weiterhin erreichbar sind.

Lassen Sie es uns mit einem kleinen Codeausschnitt demonstrieren:

const-Elemente = []; 
const list = document.getElementById('Liste'); 
 
Funktion addElement() { 
    // Knoten bereinigen 
    Liste.innerHTML = ''; 
 
    const divElement = document.createElement('div'); 
    const element = document.createTextNode(`Element ${elements.length} hinzufügen`); 
    divElement.appendChild(element); 
 
 
    list.appendChild(divElement); 
    Elemente.push(divElement); 
} 
 
document.getElementById('addElement').onclick = addElement; 


Hinweis: Die Funktion „addElement“ löscht das Listen-Div und fügt ihm ein neues Element als untergeordnetes Element hinzu. Das neu erstellte Element wird dem Element-Array hinzugefügt.

Bei der nächsten Ausführung addElement wird das Element aus der Listen-Div entfernt, es ist jedoch nicht für die Garbage Collection geeignet, da es im elements -Array gespeichert ist.

Wir überwachen die Funktion nach mehrmaliger Ausführung:


Sehen Sie im Screenshot oben, wie der Knoten durchgesickert ist. Wie lässt sich das Problem beheben? Durch das Löschen elements Arrays werden diese für die Speicherbereinigung freigegeben.

Zusammenfassen:

In diesem Artikel haben wir uns die häufigsten Arten von Speicherlecks angesehen. Offensichtlich verliert JavaScript selbst keinen Speicher. Die Ursache liegt vielmehr in einer unbeabsichtigten Speicherung im Speicher seitens des Entwicklers. Solange der Code sauber ist und wir nicht vergessen, hinterher aufzuräumen, kommt es nicht zu Lecks.

Sie müssen verstehen, wie Speicher und Garbage Collection in JavaScript funktionieren. Manche Entwickler verstehen das falsch und glauben, dass sie sich darüber keine Gedanken machen müssen, weil alles automatisch abläuft.

Dies ist das Ende dieses Artikels über häufige JavaScript Speicherfehler. Weitere Informationen zu JavaScript-Speicherfehlern finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen!

Das könnte Sie auch interessieren:
  • Häufige JavaScript-Speicherfehler und -Lösungen
  • JavaScript-Missverständnis: Kümmern Sie sich nicht um die Speicherverwaltung

<<:  Beispielcode zum Implementieren schöner Uhranimationseffekte mit CSS

>>:  Detaillierte Erläuterung der Vorgänge und Implementierungen im Zusammenhang mit dem HTML-Druck

Artikel empfehlen

So zeigen Sie den Rahmen an, wenn td leer ist

Zuvor habe ich zusammengefasst, wie man mit CSS di...

Beispiele für die korrekte Verwendung von Karten in WeChat-Miniprogrammen

Inhaltsverzeichnis Vorwort 1. Vorbereitung 2. Tat...

Prozessdiagramm zur Implementierung der Zabbix WEB-Überwachung

Nehmen Sie als Beispiel die WEB-Schnittstelle von...

Typische Fälle von MySQL-Indexfehlern

Inhaltsverzeichnis Typische Fälle Anhang: Häufige...

Nginx-Konfiguration zum Erreichen eines Lastenausgleichs auf mehreren Servern

Nginx-Lastausgleichsserver: IP: 192.168.0.4 (Ngin...

Fallstricke und Lösungen für das Upgrade von MySQL 5.7.23 in CentOS 7

Vorwort Kürzlich bin ich beim Upgrade von MySQL 5...

Mehrere Implementierungsmethoden der Tab-Leiste (empfohlen)

Registerkarten: Kategorie + Beschreibung Tag-Leis...

Analysieren Sie die Prinzipien und Methoden der MySQL-Replikation und -Optimierung

1. Einleitung MySQL verfügt über eine Replikation...

React realisiert sekundären Verknüpfungseffekt (Treppeneffekt)

In diesem Artikel wird der spezifische Code von R...