C/C と比較して、私たちが使用する JavaScript のメモリ処理により、開発中にビジネス ロジックの記述により注意を払うことができます。しかし、ビジネスの継続的な複雑化と、シングルページ アプリケーション、モバイル HTML5 アプリケーション、Node.js プログラムなどの開発により、JavaScript のメモリ問題によって引き起こされる遅延やメモリ オーバーフローなどの現象は、もはや珍しいものではなくなりました。
この記事では、JavaScript の言語レベルからメモリ使用量と最適化について説明します。誰もがよく知っている、少しは聞いたことのあるところから、あまり気づかないところまで、一つ一つ分析していきます。
1. 言語レベルのメモリ管理
1.1 範囲
スコープは JavaScript プログラミングにおいて非常に重要な操作メカニズムであり、同期 JavaScript プログラミングではあまり注目されませんが、非同期プログラミングでは、優れたスコープ制御スキルが JavaScript 開発の鍵となります。さらに、スコープは JavaScript のメモリ管理において重要な役割を果たします。
JavaScript では、ステートメントとグローバル スコープを使用した関数呼び出しによってスコープを形成できます。
次のコードを例に挙げます。
foo() 関数では、var ステートメントを使用してローカル変数を宣言および定義します。スコープが関数本体内に形成されるため、この変数はスコープ内で定義されます。また、foo()関数本体ではスコープ拡張処理を行っていないため、関数実行後にローカル変数も破棄されます。外部スコープでは変数にアクセスできません。
bar() 関数では、ローカル変数は var ステートメントを使用して宣言されず、代わりにグローバル変数として直接定義されます。したがって、外側のスコープはこの変数にアクセスできます。
JavaScript では、変数識別子の検索は現在のスコープから開始され、グローバル スコープまで外側に向かって検索されます。したがって、JavaScript コード内の変数へのアクセスは外部からのみ行うことができ、その逆はできません。
baz() 関数の実行により、グローバル スコープでグローバル変数 val が定義されます。 bar() 関数では、識別子 val にアクセスするとき、検索原則は内側から外側へです。bar 関数のスコープ内で見つからない場合は、上位レベル、つまり foo のスコープに進みます。 () 関数の範囲内で検索します。
1.3 終了
JavaScript での識別子の検索はインサイドアウトの原則に従っていることはわかっています。ただし、ビジネス ロジックが複雑なため、単一の配信シーケンスでは増大する新たなニーズを満たすには程遠いです。まず次のコードを見てみましょう:
ここで示した、外側のスコープが内側のスコープにアクセスできるようにするテクノロジーは、クロージャ (Closure) です。高階関数の適用のおかげで、foo() 関数のスコープが「拡張」されました。
foo() 関数は、foo() 関数のスコープ内に存在する匿名関数を返すため、foo() 関数のスコープ内でローカル変数にアクセスし、その参照を保存できます。この関数はローカル変数を直接返すため、外側のスコープで bar() 関数を直接実行してローカル変数を取得できます。
クロージャは JavaScript の高度な機能であり、これを使用してさまざまなニーズを満たす、より複雑な効果を実現できます。ただし、内部変数参照を持つ関数は関数から取り出されるため、関数実行後、内部変数への参照がすべて解放されるまでスコープ内の変数が必ずしも破棄されるわけではないことに注意してください。したがって、クロージャを適用すると、メモリを解放できなくなりやすくなります。
2. JavaScript のメモリ再利用メカニズム
ここでは、Chrome と Node.js で使用されている Google が発表した V8 エンジンを例として、JavaScript のメモリ リサイクル メカニズムを簡単に紹介します。さらに詳しい内容については、私の友人である Pu Ling の著書「Speaking in」を購入してください。シンプルで奥深い言語「Node.js」を学ぶには、「メモリ制御」の章にかなり詳しく紹介されています。
V8 では、すべての JavaScript オブジェクトに「ヒープ」を通じてメモリが割り当てられます。
コード内で変数を宣言して値を割り当てると、V8 はヒープ メモリの一部をその変数に割り当てます。割り当てられたメモリがこの変数を格納するのに十分でない場合、V8 はヒープ サイズが V8 のメモリ制限に達するまでメモリを適用し続けます。デフォルトでは、V8 のヒープ メモリ サイズの上限は、64 ビット システムでは 1464 MB、32 ビット システムでは 732 MB で、それぞれ約 1.4 GB と 0.7 GB です。
さらに、V8 はヒープ メモリ内の JavaScript オブジェクトを世代 (新世代と旧世代) ごとに管理します。新しい世代は、一時変数、文字列など、ライフサイクルの短い JavaScript オブジェクトを指しますが、古い世代は、メイン コントローラー、サーバー オブジェクトなど、複数のガベージ コレクションを経てライフ サイクルが長いオブジェクトを指します。 、など。
ガベージ コレクション アルゴリズムは常にプログラミング言語開発の重要な部分であり、V8 で使用されるガベージ コレクション アルゴリズムには主に次のものが含まれます。
1. スカバンジアルゴリズム: コピーによるメモリ空間管理。主に新世代のメモリ空間に使用されます。
2. マークスイープアルゴリズムとマークコンパクトアルゴリズム: マーキングによるヒープメモリの整理と整理。主に古い世代のオブジェクトの検査とリサイクルに使用されます。
追記: V8 ガベージ コレクションの実装の詳細については、関連する書籍、ドキュメント、ソース コードを読むことで学ぶことができます。
JavaScript エンジンがどのような状況でどのオブジェクトをリサイクルするかを見てみましょう。
2.1 範囲と参照
初心者は、関数の実行が完了すると、関数内で宣言されたオブジェクトが破棄されると誤解することがよくあります。しかし実際には、この理解は厳密かつ包括的ではなく、簡単に混乱を招く可能性があります。
参照は JavaScript プログラミングにおいて非常に重要なメカニズムですが、奇妙なことに、ほとんどの開発者はそれに注意を払っておらず、理解さえしていません。参照は、「コードによるオブジェクトへのアクセス」という抽象的な関係を指します。これは C/C ポインターに似ていますが、同じものではありません。参照は、JavaScript エンジンによるガベージ コレクションの最も重要なメカニズムでもあります。
例として次のコードを取り上げます。
このコードを読んで、コードのこの部分が実行された後もどのオブジェクトがまだ生きているかわかりますか?
関連する原則によれば、このコード内のリサイクルされていないオブジェクトには val と bar() が含まれます。これらがリサイクルできない理由は何ですか?
JavaScript エンジンはガベージ コレクションをどのように実行しますか?前述のガベージ コレクション アルゴリズムは、リサイクル中にのみ使用されます。では、どのオブジェクトがリサイクル可能で、どのオブジェクトが存続する必要があるかをどのように判断するのでしょうか?答えは、JavaScript オブジェクトへの参照です。
JavaScript コードでは、何も操作せずに変数名を別行で記述するだけでも、JavaScript エンジンはこれがオブジェクトへのアクセス動作であると認識し、オブジェクトへの参照が存在します。ガベージ コレクションの動作がプログラム ロジックの動作に影響を与えないようにするために、JavaScript エンジンは使用されているオブジェクトをリサイクルしてはなりません。リサイクルしないと混乱が生じます。したがって、オブジェクトが使用中かどうかを判断する基準は、そのオブジェクトへの参照がまだ存在するかどうかです。しかし実際には、これは妥協策です。JavaScript 参照は転送できるため、一部の参照はグローバル スコープに持ち込まれる可能性がありますが、実際には、アクセスされたらリサイクルする必要があるため、ビジネス ロジックでそれらを変更する必要はありません。しかし、JavaScript エンジンは依然としてプログラムがそれを必要としていると固く信じます。
変数と参照を正しく使用する方法は、言語レベルから JavaScript を最適化するための鍵です。
3. JavaScript を最適化します
ようやく本題に入ります。ここまで読んでいただき、ありがとうございました。これで、JavaScript のメモリ管理メカニズムについては十分に理解できたと思います。その後、次のテクニックを使用すると、さらに強力になります。 。
3.1 機能を上手に活用する
優れた JavaScript プロジェクトを読む習慣がある方は、多くの専門家がフロントエンド JavaScript コードを開発する際に、コードの最外層をラップするために匿名関数をよく使用していることがわかるでしょう。
これを行うことの利点は何ですか?記事の冒頭で述べたように、JavaScript のスコープにはステートメントとグローバル スコープを伴う関数呼び出しが含まれていることは誰もが知っています。また、グローバル スコープで定義されたオブジェクトは、大きなオブジェクトの場合、プロセスが終了するまで存続する可能性が高く、問題が発生することもわかっています。たとえば、JavaScript でテンプレートのレンダリングを行うことを好む人もいます。
この種のコードは新しい作品で頻繁に見られますが、ここに何か問題がありますか? データ プールから取得したデータの量が通常よりも大きい場合、フロントエンドがモールド プレートの汚染を完了した後、データ量がこの量は、完全なアクション ドメイン内で定義されているため、JavaScript によってバックグラウンドでブロックされることはありません。
が、コードの外装の 1 層の関数にいくつかの非常に単純な変更を加えた場合、その効果は大きく異なります。UI の染まりが完了すると、データの参照も同時にキャンセルされ、最終的にはレイヤー関数の実行が完了すると、JavaScript 引数がその中のオブジェクトの検査を開始し、データも同時に取得されます。 3.2 绝对不要定义全局变量
我们刚才也谈到了,当一变量被定义在全局作用域中,默认情况下JavaScript 引擎就不会将其回收销毁このように、この量は、トップが遮断されるまで、老朽化したスタック内に存在し続けます。
私たちは、原則に忠実に従う: 全局所量を不必要に使用することは禁止されている。しかし、全局所量は、開発当初は非常に節約されているが、それによって生じる問題は、その利点に比べてより重い。
量が回収されにくくする;
2. 作業領域内で混乱しやすくなる。 『全局所量』はpackage関数により処理されます。
3.3 手動での量参照のキャンセル
复制代
复制代码
コールバック関数は Continuation Passing Style (CPS) テクノロジであり、このスタイルのプログラミングでは、関数のビジネスの焦点が戻り値からコールバック関数に移されます。そして、クロージャに比べて多くの利点があります:
1. 渡されたパラメータが基本型 (文字列、数値など) の場合、ビジネス コードを使用した後、値がコピーされます。 >2 .コールバックを通じて、同期リクエストを完了するだけでなく、現在非常に人気のある記述スタイルである非同期プログラミングでも使用できます。
3. コールバック関数自体は、通常、リクエスト後に一時的な匿名関数です。関数が実行されると、コールバック関数自体の参照が解放され、それ自体がリサイクルされます。
3.5 適切な閉鎖管理
ビジネス要件 (ループ イベント バインディング、プライベート プロパティ、パラメーターを含むコールバックなど) でクロージャーを使用する必要がある場合は、詳細に注意してください。ループ バインディング イベントは、JavaScript クロージャを始めるための必須コースであると言えます。6 つのボタンがあり、ユーザーが 6 種類のイベントに対応してボタンをクリックすると、対応するイベントが出力されます。指定された場所で。
ここでの最初の解決策は明らかに典型的なループ バインディング イベント エラーです。詳細については、私がネットユーザーに提供した回答を参照してください。2 番目と 3 番目の解決策の違いは次のとおりです。受信クロージャパラメータ内。
2 番目のソリューションで渡されるパラメーターは現在のループの添字ですが、後者は対応するイベント オブジェクトを直接渡します。実際、後者の方が大規模なデータ アプリケーションに適しています。JavaScript 関数型プログラミングでは、関数の呼び出し時に渡されるパラメーターは基本型オブジェクトであるため、関数本体で取得される仮パラメーターはコピーされた値になります。この値は、関数本体のスコープ内のローカル変数として定義されます。イベント バインディングの完了後、イベント変数を手動で逆参照して、外側のスコープでのメモリ使用量を削減できます。また、要素が削除されると、対応するイベント リスニング関数、イベント オブジェクト、およびクロージャー関数も破棄され、リサイクルされます。
3.6 メモリはキャッシュではありません
キャッシュはビジネス開発において重要な役割を果たし、時間とスペースのリソースの負担を軽減できます。ただし、メモリを安易にキャッシュとして使用しないように注意してください。メモリはプログラム開発にとって貴重なリソースです。それが非常に重要なリソースでない場合は、メモリに直接配置しないようにするか、期限切れのキャッシュを自動的に破棄する有効期限メカニズムを開発してください。
4. JavaScript のメモリ使用量を確認します
日々の開発では、いくつかのツールを使用して JavaScript のメモリ使用量を分析し、トラブルシューティングすることもできます。
4.1 ブリンク / Webkit ブラウザ
Blink/Webkit ブラウザ (Chrome、Safari、Opera など) では、開発者ツールのプロファイル ツールを使用してプログラムのメモリをチェックできます。
4.2 Node.js でのメモリチェック
Node.js では、メモリ チェックに node-heapdump モジュールと node-memwatch モジュールを使用できます。
In this way, there will be a snapshot file named in the format of heapdump-
5. Summary
The end of the article soon came. This sharing mainly shows you the following points:
1. JavaScript is closely related to memory usage at the language level;
2. Memory management and recycling mechanism in JavaScript;
3. How to use memory more efficiently so that the produced JavaScript Can have more vitality for expansion;
4. How to perform memory check when encountering memory problems.
I hope that by studying this article, you can produce better JavaScript code, which will reassure your mother and your boss.