ホームページ  >  記事  >  ウェブフロントエンド  >  JS でのメモリ リークを防ぐにはどうすればよいですか?よくある 5 つの記憶エラーについて話しましょう

JS でのメモリ リークを防ぐにはどうすればよいですか?よくある 5 つの記憶エラーについて話しましょう

青灯夜游
青灯夜游転載
2021-12-31 10:57:281838ブラウズ

JS でのメモリ リークを防ぐにはどうすればよいですか?よくある 5 つの記憶エラーについて話しましょう

#JavaScript はメモリ管理操作を提供しません。代わりに、メモリは、ガベージ コレクションと呼ばれるメモリ再利用プロセスを通じて JavaScript VM によって管理されます。

ガベージ コレクションを強制することはできないので、ガベージ コレクションが適切に機能することをどのように確認すればよいでしょうか?私たちはそれについてどれくらい知っていますか?

このプロセス中はスクリプトの実行が一時停止されます

    アクセスできないリソースのメモリが解放されます
  • 未定義です
  • メモリ全体をチェックするわけではありません
  • これは予測不可能ですが、必要に応じて実行されます
  • これは、リソースとメモリの分散の問題を心配する必要がないことを意味しますか? もちろんそうではありません。注意しないと、メモリ リークが発生する可能性があります。

#メモリ リークとは何ですか?

JS でのメモリ リークを防ぐにはどうすればよいですか?よくある 5 つの記憶エラーについて話しましょう#メモリ リークとは、ソフトウェアによって再利用できない、割り当てられたメモリのブロックです。

Javascript はガベージ コレクターを提供しますが、これはメモリ リークを回避できることを意味するものではありません。ガベージ コレクションの対象となるには、オブジェクトが他の場所で参照されていてはなりません。未使用のリソースへの参照を保持すると、それらのリソースが再利用されなくなります。これは、

無意識の記憶保持と呼ばれます。

メモリのリークにより、ガベージ コレクターがより頻繁に実行される可能性があります。このプロセスによりスクリプトの実行が妨げられるため、プログラムがフリーズする可能性があります。このような遅延が発生すると、うるさいユーザーは、満足できない場合は製品が長時間オフラインになることに間違いなく気づくでしょう。さらに深刻なことに、アプリケーション全体がクラッシュする可能性があります。これは gg です。

メモリ リークを防ぐにはどうすればよいでしょうか? 重要なのは、不必要なリソースを保持しないようにすることです。いくつかの一般的なシナリオを見てみましょう。

1. タイマー監視

setInterval() メソッドは、毎回関数を繰り返し呼び出すか、コード フラグメントを実行します。呼び出しの間には固定の時間遅延があります。間隔を一意に識別する間隔 ID

が返されるため、後で

clearInterval() を呼び出して削除できます。 # ループの後に完了したことを示すコールバック関数を呼び出すコンポーネントを作成します。この例では React を使用していますが、これはどの FE フレームワークでも機能します。

import React, { useRef } from 'react';

const Timer = ({ cicles, onFinish }) => {
    const currentCicles = useRef(0);

    setInterval(() => {
        if (currentCicles.current >= cicles) {
            onFinish();
            return;
        }
        currentCicles.current++;
    }, 500);

    return (
        <div>Loading ...</div>
    );
}

export default Timer;
一見すると問題ないようです。心配しないで、このタイマーをトリガーする別のコンポーネントを作成し、そのメモリ パフォーマンスを分析しましょう。
import React, { useState } from &#39;react&#39;;
import styles from &#39;../styles/Home.module.css&#39;
import Timer from &#39;../components/Timer&#39;;

export default function Home() {
    const [showTimer, setShowTimer] = useState();
    const onFinish = () => setShowTimer(false);

    return (
      <div className={styles.container}>
          {showTimer ? (
              <Timer cicles={10} onFinish={onFinish} />
          ): (
              <button onClick={() => setShowTimer(true)}>
                Retry
              </button>
          )}
      </div>
    )
}

Retry ボタンを数回クリックした後、Chrome デベロッパー ツールを使用してメモリ使用量を取得した結果が次のとおりです:

再試行ボタンをクリックすると、より多くのメモリが割り当てられることがわかります。これは、以前に割り当てられたメモリが解放されていないことを意味します。タイマーは交換されずにまだ動作しています。 この問題を解決するにはどうすればよいですか?

setInterval

の戻り値は間隔 ID であり、この間隔をキャンセルするために使用できます。この特定のケースでは、コンポーネントがアンロードされた後に JS でのメモリ リークを防ぐにはどうすればよいですか?よくある 5 つの記憶エラーについて話しましょうclearInterval

を呼び出すことができます。

useEffect(() => {
    const intervalId = setInterval(() => {
        if (currentCicles.current >= cicles) {
            onFinish();
            return;
        }
        currentCicles.current++;
    }, 500);

    return () => clearInterval(intervalId);
}, [])
コードを作成するときにこの問題を見つけるのが難しい場合がありますが、最善の方法はコンポーネントを抽象化することです。

ここでは React が使用されており、このロジックすべてをカスタム フックにラップできます。

import { useEffect } from 'react';

export const useTimeout = (refreshCycle = 100, callback) => {
    useEffect(() => {
        if (refreshCycle  {
            callback();
        }, refreshCycle);

        return () => clearInterval(intervalId);
    }, [refreshCycle, setInterval, clearInterval]);
};

export default useTimeout;
setInterval を使用する必要がある場合は、次のようにすることができます:

const handleTimeout = () => ...;

useTimeout(100, handleTimeout);
これで、心配することなく、この

useTimeout フック

を使用できるようになります。メモリのリークについてですが、これも抽象化の利点です。

2. イベント リスニング

Web API は多数のイベント リスナーを提供します。前に、

setTimeout について説明しました。次に、addEventListener を見てみましょう。

この例では、キーボード ショートカット関数を作成します。さまざまなページにさまざまな機能があるため、さまざまなショートカット キー機能が作成されます。

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);
は、2 番目の addEventListener を実行する前にクリーンアップがないことを除けば、引き続き問題ないようです。 keyup## #。このコードは、keyup

リスナーを置き換えるのではなく、別の

callback

を追加します。これは、キーが押されると 2 つの機能がトリガーされることを意味します。 前のコールバックをクリアするには、removeEventListener :<pre class="brush:php;toolbar:false">document.removeEventListener(‘keyup’, homeShortcuts);</pre>上記のコードをリファクタリングする必要があります: <pre class="brush:php;toolbar:false">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);</pre>経験によれば、from を使用する場合は、グローバル オブジェクト ツールを使用する場合は、細心の注意を払ってください。

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

4. Window Object

向 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

5. 持有DOM引用

DOM节点也不能避免内存泄漏。我们需要注意不要保存它们的引用。否则,垃圾回收器将无法清理它们,因为它们仍然是可访问的。

用一小段代码演示一下:

const elements = [];
const list = document.getElementById(&#39;list&#39;);

function addElement() {
    // clean nodes
    list.innerHTML = &#39;&#39;;

    const divElement= document.createElement(&#39;div&#39;);
    const element = document.createTextNode(`adding element ${elements.length}`);
    divElement.appendChild(element);


    list.appendChild(divElement);
    elements.push(divElement);
}

document.getElementById(&#39;addElement&#39;).onclick = addElement;

注意,addElement 函数清除列表 div,并将一个新元素作为子元素添加到它中。这个新创建的元素被添加到 elements 数组中。

下一次执行 addElement 时,该元素将从列表 div 中删除,但是它不适合进行垃圾收集,因为它存储在 elements 数组中。

我们在执行几次之后监视函数:

JS でのメモリ リークを防ぐにはどうすればよいですか?よくある 5 つの記憶エラーについて話しましょう

在上面的截图中看到节点是如何被泄露的。那怎么解决这个问题?清除 elements  数组将使它们有资格进行垃圾收集。

总结

在这篇文章中,我们已经看到了最常见的内存泄露方式。很明显,JavaScript本身并没有泄漏内存。相反,它是由开发者方面无意的内存保持造成的。只要代码是整洁的,而且我们不忘自己清理,就不会发生泄漏。

了解内存和垃圾回收在JavaScript中是如何工作的是必须的。一些开发者得到了错误的意识,认为由于它是自动的,所以他们不需要担心这个问题。

原文地址:https://betterprogramming.pub/5-common-javascript-memory-mistakes-c8553972e4c2

作者: Jose Granja

译者:前端小智

【相关推荐:javascript学习教程

以上がJS でのメモリ リークを防ぐにはどうすればよいですか?よくある 5 つの記憶エラーについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。