JavaScript 学習チュートリアル]ガベージ コレクションを強制することはできないので、ガベージ コレクションが適切に機能することをどのように確認すればよいでしょうか?私たちはそれについてどれくらい知っていますか?
このプロセス中はスクリプトの実行が一時停止されます
#メモリ リークとは何ですか?
Javascript はガベージ コレクターを提供しますが、これはメモリ リークを回避できることを意味するものではありません。ガベージ コレクションの対象となるには、オブジェクトが他の場所で参照されていてはなりません。未使用のリソースへの参照を保持すると、それらのリソースが再利用されなくなります。これは、無意識の記憶保持と呼ばれます。
メモリのリークにより、ガベージ コレクターがより頻繁に実行される可能性があります。このプロセスによりスクリプトの実行が妨げられるため、プログラムがフリーズする可能性があります。このような遅延が発生すると、うるさいユーザーは、満足できない場合は製品が長時間オフラインになることに間違いなく気づくでしょう。さらに深刻なことに、アプリケーション全体がクラッシュする可能性があります。これは gg です。 メモリ リークを防ぐにはどうすればよいでしょうか? 重要なのは、不必要なリソースを保持しないようにすることです。いくつかの一般的なシナリオを見てみましょう。
1. タイマー監視 が返されるため、後で clearInterval()
を呼び出して削除できます。 # ループの後に完了したことを示すコールバック関数を呼び出すコンポーネントを作成します。この例では React を使用していますが、これはどの FE フレームワークでも機能します。
<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">import React, { useRef } from 'react';
const Timer = ({ cicles, onFinish }) => {
const currentCicles = useRef(0);
setInterval(() => {
if (currentCicles.current >= cicles) {
onFinish();
return;
}
currentCicles.current++;
}, 500);
return (
<p>Loading ...</p>
);
}
export default Timer;</pre><div class="contentsignin">ログイン後にコピー</div></div>
一見すると問題ないようです。心配しないで、このタイマーをトリガーする別のコンポーネントを作成し、そのメモリ パフォーマンスを分析しましょう。
import React, { useState } from 'react'; import styles from '../styles/Home.module.css' import Timer from '../components/Timer'; export default function Home() { const [showTimer, setShowTimer] = useState(); const onFinish = () => setShowTimer(false); return ( <p> {showTimer ? ( <timer></timer> ): ( <button> setShowTimer(true)}> Retry </button> )} </p> ) }
Retry
ボタンを数回クリックした後、Chrome デベロッパー ツールを使用してメモリ使用量を取得した結果が次のとおりです:
再試行ボタンをクリックすると、より多くのメモリが割り当てられることがわかります。これは、以前に割り当てられたメモリが解放されていないことを意味します。タイマーは交換されずにまだ動作しています。
setInterval の戻り値は間隔 ID であり、この間隔をキャンセルするために使用できます。この特定のケースでは、コンポーネントがアンロードされた後に
clearIntervalを呼び出すことができます。
useEffect(() => { const intervalId = setInterval(() => { if (currentCicles.current >= cicles) { onFinish(); return; } currentCicles.current++; }, 500); return () => clearInterval(intervalId); }, [])
コードを作成するときにこの問題を見つけるのが難しい場合がありますが、最善の方法はコンポーネントを抽象化することです。 ここでは React が使用されており、このロジックすべてをカスタム フックにラップできます。
<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">import { useEffect } from 'react';
export const useTimeout = (refreshCycle = 100, callback) => {
useEffect(() => {
if (refreshCycle {
callback();
}, refreshCycle);
return () => clearInterval(intervalId);
}, [refreshCycle, setInterval, clearInterval]);
};
export default useTimeout;</pre><div class="contentsignin">ログイン後にコピー</div></div>
を使用する必要がある場合は、次のようにすることができます:
const handleTimeout = () => ...; useTimeout(100, handleTimeout);
これで、心配することなく、この
useTimeout フック を使用できるようになります。メモリのリークについてですが、これも抽象化の利点です。
Web API は多数のイベント リスナーを提供します。前に、
この例では、キーボード ショートカット関数を作成します。さまざまなページにさまざまな機能があるため、さまざまなショートカット キー機能が作成されます。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">function homeShortcuts({ key}) {
if (key === 'E') {
console.log('edit widget')
}
}
// 用户在主页上登陆,我们执行
document.addEventListener('keyup', homeShortcuts);
// 用户做一些事情,然后导航到设置
function settingsShortcuts({ key}) {
if (key === 'E') {
console.log('edit setting')
}
}
// 用户在主页上登陆,我们执行
document.addEventListener('keyup', settingsShortcuts);</pre><div class="contentsignin">ログイン後にコピー</div></div>
は、2 番目の addEventListener
を実行する前にクリーンアップがないことを除けば、問題ないようです。
keyup
リスナーを置き換えるのではなく、別のcallback を追加します。これは、キーが押されると 2 つの機能がトリガーされることを意味します。
前のコールバックをクリアするには、
removeEventListener :
document.removeEventListener(‘keyup’, homeShortcuts);
上記のコードをリファクタリングする必要があります:
function homeShortcuts({ key}) { if (key === 'E') { console.log('edit widget') } } // user lands on home and we execute document.addEventListener('keyup', homeShortcuts); // user does some stuff and navigates to settings function settingsShortcuts({ key}) { if (key === 'E') { console.log('edit setting') } } // user lands on home and we execute document.removeEventListener('keyup', homeShortcuts); document.addEventListener('keyup', settingsShortcuts);
3.Observers
Observers
は、多くの開発者が知らないブラウザの Web API 関数です。これは、HTML 要素の可視性やサイズの変更を確認する場合に強力です。IntersectionObserver
接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport
)交叉状态的方法。祖先元素与视窗(viewport
)被称为根(root
)。
尽管它很强大,但我们也要谨慎的使用它。一旦完成了对对象的观察,就要记得在不用的时候取消它。
看看代码:
const ref = ... const visible = (visible) => { console.log(`It is ${visible}`); } useEffect(() => { if (!ref) { return; } observer.current = new IntersectionObserver( (entries) => { if (!entries[0].isIntersecting) { visible(true); } else { visbile(false); } }, { rootMargin: `-${header.height}px` }, ); observer.current.observe(ref); }, [ref]);
上面的代码看起来不错。然而,一旦组件被卸载,观察者会发生什么?它不会被清除,那内存可就泄漏了。我们怎么解决这个问题呢?只需要使用 disconnect
方法:
const ref = ... const visible = (visible) => { console.log(`It is ${visible}`); } useEffect(() => { if (!ref) { return; } observer.current = new IntersectionObserver( (entries) => { if (!entries[0].isIntersecting) { visible(true); } else { visbile(false); } }, { rootMargin: `-${header.height}px` }, ); observer.current.observe(ref); return () => observer.current?.disconnect(); }, [ref]);
向 Window 添加对象是一个常见的错误。在某些场景中,可能很难找到它,特别是在使用 Window Execution上下文中的this
关键字。看看下面的例子:
function addElement(element) { if (!this.stack) { this.stack = { elements: [] } } this.stack.elements.push(element); }
它看起来无害,但这取决于你从哪个上下文调用addElement
。如果你从Window Context调用addElement,那就会越堆越多。
另一个问题可能是错误地定义了一个全局变量:
var a = 'example 1'; // 作用域限定在创建var的地方 b = 'example 2'; // 添加到Window对象中
要防止这种问题可以使用严格模式:
"use strict"
通过使用严格模式,向JavaScript编译器暗示,你想保护自己免受这些行为的影响。当你需要时,你仍然可以使用Window。不过,你必须以明确的方式使用它。
严格模式是如何影响我们前面的例子:
addElement
函数,当从全局作用域调用时,this
是未定义的const | let | var
,你会得到以下错误:Uncaught ReferenceError: b is not defined
DOM节点也不能避免内存泄漏。我们需要注意不要保存它们的引用。否则,垃圾回收器将无法清理它们,因为它们仍然是可访问的。
用一小段代码演示一下:
const elements = []; const list = document.getElementById('list'); function addElement() { // clean nodes list.innerHTML = ''; const pElement= document.createElement('p'); const element = document.createTextNode(`adding element ${elements.length}`); pElement.appendChild(element); list.appendChild(pElement); elements.push(pElement); } document.getElementById('addElement').onclick = addElement;
注意,addElement
函数清除列表 p
,并将一个新元素作为子元素添加到它中。这个新创建的元素被添加到 elements
数组中。
下一次执行 addElement
时,该元素将从列表 p
中删除,但是它不适合进行垃圾收集,因为它存储在 elements
数组中。
我们在执行几次之后监视函数:
在上面的截图中看到节点是如何被泄露的。那怎么解决这个问题?清除 elements
数组将使它们有资格进行垃圾收集。
在这篇文章中,我们已经看到了最常见的内存泄露方式。很明显,JavaScript本身并没有泄漏内存。相反,它是由开发者方面无意的内存保持造成的。只要代码是整洁的,而且我们不忘自己清理,就不会发生泄漏。
了解内存和垃圾回收在JavaScript中是如何工作的是必须的。一些开发者得到了错误的意识,认为由于它是自动的,所以他们不需要担心这个问题。
作者: Jose Granja
原文:https://betterprogramming.pub/5-common-javascript-memory-mistakes-c8553972e4c2
(学习视频分享:web前端)
以上が5 つの一般的な JavaScript メモリ エラーの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。