序文クロージャは、JavaScript を理解する上で大きな難題です。インターネット上にはクロージャに関する記事がたくさんありますが、読んだ後に完全に理解できる記事はほとんどありません。その理由は、閉鎖には一連の知識ポイントが関係しているからだと思います。この一連の知識ポイントを徹底的に理解し、概念の閉ループを達成することによってのみ、それを真に理解することができます。今日は、メモリ割り当てとリサイクルという別の観点からクロージャを理解し、これまで見てきたクロージャの知識を本当に理解する手助けをしたいと考えています。同時に、この記事がクロージャについて読む最後の記事になることも願っています。 この記事の写真を見るときは、矢印の方向に注意してください。ルート オブジェクト ウィンドウがメモリ ガベージを走査するために依存している原則であるため、ウィンドウから始まる矢印に沿って見つかるものはすべてメモリ ガベージではなく、リサイクルされません。見つからないオブジェクトのみがメモリ ガベージとなり、適切なタイミングで GC によってリサイクルされます。 クロージャの紹介関数がネストされている場合、内部関数は外部関数スコープ内の変数を参照し、内部関数はグローバル環境内の変数によって参照されるため、クロージャが形成されます。
クロージャに関して特に注意する必要があることの 1 つは、関数内で定義されたすべての関数が同じクロージャ オブジェクトを共有することです。それはどういう意味ですか?次のコードを見てください。 var a 関数b() { var c = 新しい文字列('1') var d = 新しい文字列('2') 関数e() { コンソール.log(c) } 関数f(){ コンソールログ(d) } 戻り値 f } a = b() 上記のコードでは、f は変数 d を参照し、f は外部変数 a によって参照されるため、クロージャが形成され、変数 d がメモリ内に残ります。考えてみましょう。変数 c はどうでしょうか? c は使用していないようですので、メモリ内には残らないはずです。さらに、c もメモリ内に残るという事実もあります。上記のコードによって形成されるクロージャには、c と d の 2 つのメンバーが含まれます。この現象は関数内クロージャ共有と呼ばれます。 なぜこの機能に特別な注意を払う必要があるのでしょうか?この機能のため、注意しないとメモリリークを引き起こすコードを簡単に記述できてしまいます。
大まかな理解を得るために、これらのコンテンツを Google または Baidu で検索することができます。次回はブラウザの観点からクロージャを理解する方法についてお話ししますので、ここでは詳しく説明しません。 メモリのゴミを識別する方法最近のブラウザのガベージコレクションのプロセスは比較的複雑です。詳細なプロセスは自分で Google で検索できます。ここでは、メモリガベージを判別する方法についてのみ説明します。一般的に言えば、ルート オブジェクトから開始して参照をたどって見つかるものは、リサイクルできないことがわかります。参照をたどっても見つからないオブジェクトはガベージとみなされ、次のガベージ コレクション ノードでリサイクルされます。ゴミを探すことは、手がかりを追うプロセスとして理解することができます。 クロージャのメモリ表現最も単純なコードから始めて、グローバル変数の定義を見てみましょう。 var a = new String('小歌') このようなコードはメモリ内で次のように表現されます。 グローバル環境では、変数 a が定義され、文字列が割り当てられます。矢印は参照を示します。 別の関数を定義しましょう: var a = new String('小歌') 関数を教える(){ var b = new String('小谷') } メモリ構造は次のとおりです。 どれも簡単に理解できます。注意して見ると、関数オブジェクトteachに[[scopes]]という属性があることに気がつくでしょう。これは何でしょうか?この属性は関数の作成後に表示されるのはなぜですか?これを質問していただいて嬉しいです。これはクロージャを理解するための重要なポイントです。 覚えておいてください: 関数が作成されると、JavaScript エンジンは関数オブジェクトにスコープ チェーンと呼ばれるプロパティを添付します。このプロパティは、関数のスコープと親スコープ、そしてグローバル スコープまでを含む配列オブジェクトを指します。 したがって、上の図は、teach関数がグローバル環境で作成されるため、teachのスコープチェーンには1つのレイヤー、つまりグローバルスコープグローバルのみが存在すると簡単に理解できます。
もう一度覚えておいてください: 関数が実行されると、実行コンテキストを作成するためのスペースが要求されます。実行コンテキストには、関数が定義されたときのスコープ チェーンが含まれ、次に関数内で定義された変数とパラメーターが含まれます。関数が現在のスコープで実行されると、まず現在のスコープの下で変数が検索されます。見つからない場合は、関数が定義されたときのスコープ チェーンを検索し、グローバル スコープに到達します。変数がグローバル スコープで見つからない場合は、エラーがスローされます。 関数が実行されると、実行コンテキストが作成され、それが実際にスタック構造のメモリ空間に適用されることは誰もが知っています。関数内のローカル変数はこの空間に割り当てられます。関数が実行された後、ローカル変数は次のガベージ コレクション ノードでリサイクルされます。さて、コードをもう一度更新して、関数が実行されているときのメモリ構造を見てみましょう。 var a = new String('小歌') 関数を教える(){ var b = new String('小谷') } 教える() メモリは次のように表されます。 明らかに、この関数は実行時にローカル変数を割り当てるだけで、グローバル環境の変数とは関係がないことがわかります。したがって、window オブジェクトから参照に沿って検索すると (図の矢印)、実行コンテキストで変数 b を見つけることはできません。したがって、関数が実行された後、変数 b はリサイクルされます。 コードをもう一度更新してみましょう: var a = new String('小歌') 関数を教える(){ var b = new String('小谷') var say = 関数() { コンソールログ(b) } a = 言う } 教える() メモリは次のように表されます。 注: 灰色はルート オブジェクトから追跡できないオブジェクトを示します。 関数の実行順序:
関数が実行された後、通常は変数 b が解放されるはずです。しかし、ウィンドウに沿って検索すると b が見つかることがわかります。前述のメモリ ガベージを判別する原則によれば、b はメモリ ガベージではないため、b を解放することはできません。これが、クロージャが関数内の変数をメモリ内に保持する理由です。 コードを再度アップグレードして、クロージャによって共有されるメモリ表現を見てみましょう。 var a = 新しい文字列('0') 関数b() { var c = 新しい文字列('1') var d = 新しい文字列('2') 関数e() { コンソール.log(c) } 関数f(){ コンソールログ(d) } 戻り値 f } a = b() 灰色のグラフィックはメモリ ガベージであり、ガベージ コレクターによってリサイクルされます。 上の図から、関数 f は変数 c を使用していないものの、関数 e によって c が参照されているため、変数 c がクロージャ closure 内に存在することが簡単にわかります。変数 c は window オブジェクトから開始して見つけることができるため、変数 c を解放することはできません。 この機能がどのようにしてメモリリークを引き起こすのか疑問に思うかもしれません。さて、典型的な Meteor メモリ リーク問題である次のコードを考えてみましょう。 var t = null; var replaceThing = 関数() { var o = t var 未使用 = 関数() { もし(o) console.log("こんにちは") } t = { longStr: 新しい配列(1000000).join('*'), いくつかのメソッド: 関数() { コンソール.log(1) } } } 間隔を設定します(replaceThing, 1000) このコードにはメモリリークがあります。このコードをブラウザで実行すると、メモリが増加し続けていることがわかります。GC によってメモリの一部が解放されますが、解放できないメモリがまだ残っており、勾配をつけて増加しています。下記の通り この曲線はメモリ リークがあることを示しています。開発者ツールを使用して、どのオブジェクトがリサイクルされていないかを分析できます。実際のところ、解放されないメモリは、毎回作成される大きなオブジェクトであると言えます。これを絵で描いてみましょう: 上の図では、replaceThing 関数が 3 回実行されることを前提としています。変数 t に大きなオブジェクトを割り当てるたびに、クロージャの共有により、以前の大きなオブジェクトは window オブジェクトから追跡できるため、これらの大きなオブジェクトはリサイクルできないことがわかります。実際、私たちにとって本当に役立つのは、前回 t に割り当てられた大きなオブジェクトであり、そのため、前のオブジェクトがメモリ リークを引き起こしました。
この問題の解決方法も非常に簡単です。コードが実行されるたびに、変数 o を null に設定します。試してみてください。 結論ブラウザがクロージャをどのように認識するかについての記事はこれで終わりです。ブラウザがクロージャをどのように認識するかについての詳細は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: CSS3 の display:grid、グリッドレイアウトの紹介
>>: iframe タグの使用方法の詳細な説明 (属性、透明度、適応高さ)
最近 Django を導入しましたが、MySQL を手動でインストールしたくなかったので、Docke...
目次基本設定エントリファイル main.jsアプリ.vue表紙ヘッダー検索バー本体当プロジェクトでは...
目次1. はじめに2. 使用方法ステートレスコンポーネントステートフルコンポーネント制御コンポーネン...
データ分析会社Net Market Shareによると、Linuxデスクトップオペレーティングシステ...
1. MySQL rpm パッケージのインストール # インストールソースをダウンロードします [r...
MySQL 5.7.9 のインストールチュートリアルを録画してみんなと共有しましょう環境の紹介:オペ...
目次文章1. 機械を準備する2. Dockerをインストールする1. 依存パッケージをインストールす...
問題: IIS を通じて公開された Web サイトは F5 デバイスの背後に配置されています。透過的...
<base target=_blank> は、基本リンクのターゲット フレームを新しいペ...
最初の方法: Junge のワンクリック スクリプトを使用して、LNMP 環境で MYSQL データ...
1. CSS スプライトを使用します。利点は、CSS で使用される小さな画像を 1 つの大きな画像に...
コンピュータ システムが再インストールされ、侵入テスト学習環境 DVWA を再インストールする必要が...
今日は簡単な3Dルービックキューブを作ってみましょうまずはレンダリングを見てみましょう!これを学んだ...
一言で言えば: データハイジャック (Object.defineProperty) + パブリッシュ...
導入定期的にヘルスチェックを送信して、アップストリーム グループ内の HTTP サーバーのヘルスを監...