
熟練度コースへのフロントエンド (vue) エントリー: JavaScript を学習するためにエントリーすると、メモリ管理操作は提供されません。代わりに、メモリは、ガベージ コレクションと呼ばれるメモリ再利用プロセスを通じて JavaScript VM によって管理されます。
ガベージ コレクションを強制することはできないので、ガベージ コレクションが機能していることをどのように確認すればよいでしょうか?私たちはそれについてどれくらい知っていますか?
このプロセス中はスクリプトの実行が一時停止されます
アクセスできないリソースのメモリを解放します。
それは不確実です
メモリ全体を一度にチェックするのではなく、複数のサイクルで実行します。
予測不可能ですが、必要に応じて実行されます
これは、リソースとメモリの割り当ての問題を心配する必要がないことを意味しますか? もちろんそうではありません。注意しないと、メモリ リークが発生する可能性があります。

メモリ リークとは、ソフトウェアが再利用できない割り当てられたメモリのブロックです。
Javascript はガベージ コレクターを提供しますが、だからといってメモリ リークを回避できるわけではありません。ガベージ コレクションの対象となるには、オブジェクトが他の場所で参照されていてはなりません。未使用のリソースへの参照を保持すると、それらのリソースが再利用されなくなります。これは無意識の記憶保持と呼ばれます。
メモリ リークにより、ガベージ コレクタがより頻繁に実行される可能性があります。このプロセスによりスクリプトの実行が妨げられるため、プログラムがフリーズする可能性があります。このような遅延が発生すると、うるさいユーザーは、これに満足できない場合、製品が長期間オフラインになることに間違いなく気づきます。さらに深刻なことに、アプリケーション全体がクラッシュする可能性があります。これは gg です。
メモリリークを防ぐにはどうすればよいでしょうか? 重要なことは、不要なリソースを保持しないようにすることです。いくつかの一般的なシナリオを見てみましょう。
setInterval()メソッドは、各呼び出しの間に固定の時間遅延を設けて、関数を繰り返し呼び出すか、コード フラグメントを実行します。間隔を一意に識別する間隔ID ID返されるため、後でclearInterval()呼び出して間隔を削除できます。
コールバック関数を呼び出して、 xのループ後に終了したことを示すコンポーネントを作成します。この例では React を使用していますが、これはどの FE フレームワークでも機能します。
import React, { useRef } from 'react';
const タイマー = ({ cicles, onFinish }) => {
const currentCicles = useRef(0);
setInterval(() => {
if (currentCicles.current >= cicles) {
onFinish();
戻る;
}
currentCicles.current++;
}, 500);
戻る (
<p>読み込み中...</p>
);
}
デフォルトのタイマーをエクスポートします。一見すると何の問題もないように見えます。心配しないで、このタイマーをトリガーする別のコンポーネントを作成し、そのメモリ パフォーマンスを分析しましょう。
import React, { useState } from 'react';
「../styles/Home.module.css」からスタイルをインポートします
'../components/Timer' からタイマーをインポートします。
デフォルト関数 Home() をエクスポート {
const [showTimer, setShowTimer] = useState();
const onFinish = () => setShowTimer(false);
戻る (
<p className={styles.container}>
{ショータイマー?
<タイマー cicles={10} onFinish={onFinish} />
):(
<button onClick={() => setShowTimer(true)}>
リトライ
</ボタン>
)}
</p>
)
} Retryボタンを数回クリックした後、Chrome デベロッパー ツールを使用してメモリ使用量を取得した結果は次のとおりです。

再試行ボタンをクリックすると、より多くのメモリが割り当てられることがわかります。これは、以前に割り当てられたメモリが解放されていないことを意味します。タイマーは交換されずにまだ動作しています。
この問題を解決するにはどうすればよいでしょうか? setIntervalの戻り値は間隔 ID であり、この間隔をキャンセルするために使用できます。この特定のケースでは、コンポーネントがアンロードされた後に、 clearIntervalを呼び出すことができます。
useEffect(() => {
const intervalId = setInterval(() => {
if (currentCicles.current >= cicles) {
onFinish();
戻る;
}
currentCicles.current++;
}, 500);
return () => ClearInterval(intervalId);
}、[])コードを作成するときにこの問題を見つけるのが難しい場合があります。最善の方法は、コンポーネントを抽象化することです。
ここで React を使用すると、このすべてのロジックをカスタム フックにラップできます。
import { useEffect } から 'react';
エクスポート const useTimeout = (refreshCycle = 100、コールバック) => {
useEffect(() => {
if (refreshCycle <= 0) {
setTimeout(コールバック, 0);
戻る;
}
const intervalId = setInterval(() => {
折り返し電話();
}、refreshCycle);
return () => ClearInterval(intervalId);
}, [refreshCycle、setInterval、clearInterval]);
};
デフォルトの useTimeout をエクスポートします。これで、 setInterval使用する必要があるときはいつでも、これを行うことができます。
const handleTimeout = () => ...; useTimeout(100, handleTimeout);
これで、メモリ リークを気にせずにこのuseTimeout Hookを使用できるようになり、これも抽象化の利点です。
Web API は多数のイベント リスナーを提供します。前に、 setTimeoutについて説明しました。次に、 addEventListenerを見てみましょう。
この例では、キーボード ショートカット関数を作成します。ページごとに異なる機能があるため、異なるショートカット キー機能が作成されます。
関数 homeShortcuts({ key}) {
if (key === 'E') {
console.log('ウィジェットの編集')
}
}
// ユーザーがホームページにログインすると、document.addEventListener('keyup', homeShortcuts); が実行されます。
// ユーザーが何かを行った後、設定関数に移動します settingsShortcuts({ key}) {
if (key === 'E') {
console.log('設定の編集')
}
}
// ユーザーがホームページにログインすると、document.addEventListener('keyup', settingsShortcuts); を実行します。 2 番目のaddEventListenerの実行時に前のkeyupがクリーンアップされないことを除けば、まだ問題ないようです。このコードはkeyupリスナーを置き換えるのではなく、別のcallbackを追加します。これは、キーが押されると 2 つの機能がトリガーされることを意味します。
以前のコールバックをクリアするには、 removeEventListener使用する必要があります。
document.removeEventListener('keyup', homeShortcuts);上記のコードをリファクタリングします。
関数 homeShortcuts({ key}) {
if (key === 'E') {
console.log('ウィジェットの編集')
}
}
// ユーザーがホームに着くと実行します
document.addEventListener('keyup', homeShortcuts);
// ユーザーは何らかの処理を行い、設定に移動します
関数設定ショートカット({ key}) {
if (key === 'E') {
console.log('設定の編集')
}
}
// ユーザーがホームに着くと実行します
document.removeEventListener('keyup', homeShortcuts);
document.addEventListener('keyup', settingsShortcuts);経験則として、グローバル オブジェクトのツールを使用する場合は十分に注意してください。
オブザーバーは、多くの開発者が気づいていないブラウザー Web API 機能です。これは、HTML 要素の可視性やサイズの変更を確認する場合に強力です。
IntersectionObserverインターフェイス (Intersection Observer API の一部) は、ターゲット要素とその祖先要素またはトップレベルのドキュメントviewportとの交差ステータスを非同期的に監視するためのメソッドを提供します。祖先要素とviewport rootと呼ばれます。
強力ではありますが、使用には注意が必要です。オブジェクトの観察が終了したら、使用していないときは忘れずにキャンセルしてください。
コードを見てください。
const ref = ...
const 可視 = (可視) => {
console.log(`${visible}` です);
}
useEffect(() => {
if (!ref) {
戻る;
}
observer.current = 新しい IntersectionObserver(
(エントリ) => {
if (!entries[0].isIntersecting) {
可視(真);
} それ以外 {
可視(偽);
}
}、
{ rootMargin: `-${header.height}px` },
);
オブザーバー.current.observe(ref);
}, [参照]);上記のコードは問題ないようです。しかし、コンポーネントがアンロードされるとオブザーバーはどうなるでしょうか? クリアされず、メモリがリークされます。この問題を解決するには、次のようにdisconnectメソッドを使用します。
const ref = ...
const 可視 = (可視) => {
console.log(`${visible}` です);
}
useEffect(() => {
if (!ref) {
戻る;
}
observer.current = 新しい IntersectionObserver(
(エントリ) => {
if (!entries[0].isIntersecting) {
可視(真);
} それ以外 {
可視(偽);
}
}、
{ rootMargin: `-${header.height}px` },
);
オブザーバー.current.observe(ref);
return () => オブザーバー.current?.disconnect();
}, [参照]);ウィンドウにオブジェクトを追加するのはよくある間違いです。シナリオによっては、特にウィンドウ実行コンテキストでthisキーワードを使用する場合、見つけるのが難しい場合があります。次の例を見てください。
関数 addElement(要素) {
if (!this.stack) {
this.stack = {
要素: []
}
}
this.stack.elements.push(要素);
}無害に見えますが、 addElementどのコンテキストから呼び出すかによって異なります。 Window Context から addElement を呼び出すと、ヒープが増大します。
別の問題として、グローバル変数が間違って定義されている可能性があります。
var a = 'example 1' // スコープは var が作成された場所に限定されます b = 'example 2'; // Window オブジェクトに追加されます。
この問題を防ぐために、厳密モードを使用できます。
「厳格に使用する」
厳密モードを使用すると、これらの動作から身を守る必要があることを JavaScript コンパイラーに通知します。必要な場合は引き続き Windows を使用できます。ただし、明示的な方法で使用する必要があります。
厳密モードが前の例に与える影響:
addElement関数の場合、グローバル スコープから呼び出された場合、 this未定義です
変数にconst | let | varを指定しない場合、次のエラーが発生します。
キャッチされない参照エラー: b が定義されていません
DOM ノードもメモリ リークの影響を受けないわけではありません。それらへの参照を保存しないように注意する必要があります。そうしないと、アクセス可能な状態のままになるため、ガベージ コレクターはそれらをクリーンアップできなくなります。
小さなコードでそれを示します。
const 要素 = [];
const list = document.getElementById('list');
関数 addElement() {
// ノードをクリーンアップします
list.innerHTML = '';
const pElement= document.createElement('p');
const element = document.createTextNode(`要素 ${elements.length}` を追加します);
pElement.appendChild(要素);
list.appendChild(pElement);
要素.push(pElement);
}
document.getElementById('addElement').onclick = addElement; addElement関数はリストpクリアし、新しい要素を子要素としてリストに追加することに注意してください。この新しく作成された要素はelements配列に追加されます。
次回addElementが実行されると、要素はリストpから削除されますが、 elements配列に格納されるため、ガベージ コレクションには適していません。
関数を数回実行した後、関数を監視します。
上のスクリーンショットで、ノードがどのように侵害されたかを確認してください。では、この問題をどうやって解決すればいいのでしょうか? elements配列をクリアすると、ガベージ コレクションの対象になります。
この記事では、メモリ リークの最も一般的な方法について説明しました。 JavaScript 自体がメモリ リークを発生しないことは明らかです。むしろ、開発者側の意図しないメモリ保持によって引き起こされます。コードがクリーンであり、自分自身の後のクリーンアップを忘れない限り、リークは発生しません。
JavaScript でのメモリとガベージ コレクションの仕組みを理解することが必須です。一部の開発者は、これは自動的に行われるため、この問題について心配する必要はないと誤解しています。