js クロージャとガベージ コレクション メカニズムの例の詳細な説明

js クロージャとガベージ コレクション メカニズムの例の詳細な説明

序文

フロントエンドの学習や開発では、クロージャやガベージコレクションの仕組みが難しいことが多く、面接でもそのような問題に遭遇することがよくあります。この記事では、勉強中や仕事中にこの側面についてメモした内容を記録します。

文章

1. 終了

クロージャは、Javascript 言語の難しさであると同時に、その特徴でもあります。多くの高度なアプリケーションはクロージャに依存しています。 JavaScript 開発者にとって、クロージャを理解することは非常に重要です。

1.1 クロージャとは何ですか?

クロージャとは、別の関数の変数を参照する関数です。内部関数が外部に返されて保存されるときに生成されます。(内部関数のスコープチェーンAOは、外部関数のAOを使用します)

変数は参照されるためリサイクルされず、プライベート変数をカプセル化するために使用できますが、不必要なクロージャによってメモリ消費が増加するだけです。
クロージャはプライベート変数を保護するためのメカニズムです。関数の実行時にプライベートスコープを形成し、内部のプライベート変数を外部からの干渉から保護します。つまり、クロージャとは、子関数が親関数のローカル変数とパラメータを使用できることを意味します。

1.2 クロージャの特性

①関数ネスト機能

②関数は関数外のパラメータや変数を参照できる

③パラメータと変数はガベージコレクションメカニズムによってリサイクルされない

1.3 クロージャを理解する

私たちがよく知っているスコープ チェーンの知識に基づいて、カウンターの問題、つまり関数が呼び出されるたびにカウンターを 1 つ増やす関数を実装する方法を見てみましょう。

var カウンタ = 0;
 関数demo3(){
 コンソールログ(カウンタ+=1); 
 }
 デモ3();//1
 デモ3();//2
 var カウンタ = 5;
 デモ3(); //6

上記の方法では、カウンタの値がどこかで変更されると、カウンタは無効になります。JavaScript では、関数を関数内に埋め込むクロージャを使用してこの問題を解決します。クロージャを使用してこれを実装する方法を見てみましょう。

関数add(){
 var カウンタ = 0;
 関数 plus() を返す {
 カウンター += 1;
 返品カウンター
 } 
 }
 var count = 追加()
 コンソール.log(count()) // 1
 var カウンタ = 100
 コンソール.log(count()) // 2

上記はクロージャの使用例です。add 関数には plus 関数が埋め込まれており、count 変数は返された関数を参照します。外部関数 add が実行されるたびに、メモリ空間の一部が開かれます。外部関数のアドレスは異なり、新しいアドレスが再作成されます。plus 関数は add 関数内にネストされているため、ローカル変数 counter が生成されます。count 関数が呼び出されるたびに、ローカル変数の値が 1 ずつ増加し、実際のカウンター問題が実現されます。

1.4 クロージャの主な実装形式

ここでの学習閉鎖には主に 2 つの形式があります。

① 上記の例で使用した戻り値としての関数。

関数 showName(){
 var name="xiaomi"
 関数()を返す{
  戻り名
 }
 }
 var name1=showName()
 コンソールログ(name1())

クロージャは、別の関数内から変数を読み取ることができる関数です。クロージャは、関数の内部と関数の外部を接続するブリッジです。

②パラメータとして渡されるクロージャ

変数番号 = 15
            var foo = 関数(x){
                if(x>num){
                    コンソール.log(x)
                }
              }
            関数foo2(fnc){
                変数番号=30
                フンク(25)
            }
            foo2(foo) // 25

上記コードでは、関数 foo が関数 foo2 にパラメータとして渡されています。foo2 が実行されると、foo に 25 がパラメータとして渡されます。このとき、x>num の判定における num の値は、foo2 内の num ではなく、作成した関数のスコープ内の num、つまりグローバル num であるため、25 が出力されます。

1.5 クロージャの利点と欠点

アドバンテージ:

①関数内の変数のセキュリティを保護し、カプセル化を実装し、変数が他の環境に流れて名前の競合が発生するのを防ぎます。

② キャッシュ用に変数をメモリ上に保持する(ただし、過度の使用はメモリを消費するためデメリットもある)

③匿名の自己実行関数はメモリ消費量を削減できる

欠点:

① 上記の点の 1 つは、参照されたプライベート変数を破棄できないため、メモリ消費量が増加し、メモリ リークが発生することです。解決策は、変数を使用した後に手動で null を割り当てることです。

② 次に、クロージャはクロスドメインアクセスを伴うため、パフォーマンスの低下を引き起こします。クロススコープ変数をローカル変数に格納し、ローカル変数に直接アクセスすることで、実行速度への影響を軽減できます。

1.6 クロージャの使用

(var i = 0; i < 5; i++) の場合 {
   setTimeout(関数() {
   コンソールログ( i);
   }, 1000);
  }

  コンソールにログ出力します。

上の質問を見てみましょう。これは非常に一般的な質問ですが、出力はどうなるでしょうか? ほとんどの人は、出力結果が 5、5、5、5、5、5 であることを知っています。注意深く観察すると、この問題には多くの巧妙な側面があることに気付くかもしれません。これらの 6 つの 5 の具体的な出力順序は何ですか? 5 -> 5,5,5,5,5,5。同期と非同期を理解している人なら、この状況は簡単に理解できるでしょう。上記の質問に基づいて、5 -> 0,1,2,3,4 の連続出力を実現する方法を考えてみましょう。

(var i = 0; i < 5; i++) の場合 {
 (function(j) { // j = i
  setTimeout(関数() {
  コンソールログ(j);
  }, 1000);
 })(私);
 }
 コンソールログ( i);
//5 -> 0,1,2,3,4

このように、for ループに匿名関数が追加されます。匿名関数の入力パラメータは、毎回の i の値です。同期関数が 5 を出力してから 1 秒後、01234 を出力し続けます。

(var i = 0; i < 5; i++) の場合 {
 setTimeout(関数(j) {
  コンソールにログ出力します。
 }, 1000, i);
 }
 コンソールログ( i);
 //5 -> 0,1,2,3,4

setTimeout API をよく見ると、3 番目のパラメーターがあることがわかります。これにより、匿名関数を介して i を渡すという問題が回避されます。

var 出力 = 関数 (i) {
   setTimeout(関数() {
   コンソールにログ出力します。
   }, 1000);
  };

  (var i = 0; i < 5; i++) の場合 {
   output(i); // ここで渡されたiの値がコピーされます}

  コンソールにログ出力します。
  //5 -> 0,1,2,3,4

ここでは、クロージャを使用して関数式を for ループのパラメータとして渡し、上記の効果も実現します。

(i = 0; i < 5; i++ とします) {
      setTimeout(関数() {
          console.log(新しい日付、i);
      }, 1000);
  }
  console.log(新しい日付、i);
  //5 -> 0,1,2,3,4

let ブロックのスコープを知っている人は上記の方法を思いつくでしょう。しかし、0 -> 1 -> 2 -> 3 -> 4 -> 5 のような効果を実現したい場合はどうすればよいでしょうか?

(var i = 0; i < 5; i++) の場合 {
   (関数(j) {
   setTimeout(関数() {
    console.log(新しい日付、j);
   }, 1000 * j); // ここで0〜4のタイマー時間を変更します })(i);
  }

  setTimeout(function() { // ここでタイマーを追加し、タイムアウトを 5 秒に設定します console.log(new Date, i);
  }, 1000 * i);
  //0 -> 1 -> 2 -> 3 -> 4 -> 5

Promise を通じて実装された次のコードもあります。

定数タスク = [];
  for (var i = 0; i < 5; i++) { // ここでの i の宣言は let に変更できません。変更したい場合はどうすればよいでしょうか?
   ((j) => {
   タスクをプッシュ(新しいPromise((解決) => {
    タイムアウトを設定する(() => {
    console.log(新しい日付、j);
    解決(); // ここで解決する必要があります。そうしないと、コードは期待どおりに動作しません。
    }, 1000 * j); // タイマーのタイムアウトが徐々に増加します }));
   })(私);
  }

  Promise.all(tasks).then(() => {
   タイムアウトを設定する(() => {
   console.log(新しい日付、i);
   }, 1000); // タイムアウトを 1 秒に設定するだけでよいことに注意してください });
  //0 -> 1 -> 2 -> 3 -> 4 -> 5
const task = []; // 非同期操作のPromiseはこれです
  const 出力 = (i) => 新しい Promise((resolve) => {
   タイムアウトを設定する(() => {
   console.log(新しい日付、i);
   解決する();
   }, 1000 * i);
  });

  // すべての非同期操作を生成する for (var i = 0; i < 5; i++) {
   タスクをプッシュします(出力(i));
  }

  //非同期操作が完了したら、最後のiを出力します
  Promise.all(tasks).then(() => {
   タイムアウトを設定する(() => {
   console.log(新しい日付、i);
   }, 1000);
  });
  //0 -> 1 -> 2 -> 3 -> 4 -> 5
// 他の言語でスリープをシミュレートします。実際には任意の非同期操作が可能です。const sleep = (timeoutMS) => new Promise((resolve) => {
 タイムアウトを設定します(解決、タイムアウトMS);
});

(async () => { // 宣言時に実行される非同期関数式 for (var i = 0; i < 5; i++) {
 もし (i > 0) {
  スリープ(1000)を待機します。
 }
 console.log(新しい日付、i);
 }

 スリープ(1000)を待機します。
 console.log(新しい日付、i);
})();
//0 -> 1 -> 2 -> 3 -> 4 -> 5

上記のコードではクロージャが使用されています。簡単に言うと、クロージャは親関数内の同じアドレスにある対応する変数の最終値を見つけます。

2. ガベージコレクションの仕組み

JavaScript のメモリ管理は自動的かつ目に見えない形で行われます。プリミティブ型、オブジェクト、関数などを作成しますが、これらすべてにメモリが必要です。

一般的に使用されるガベージ コレクションの方法には、マーク スイープと参照カウントの 2 つがあります。

1. マークアンドスイープ

ガベージ コレクターが実行されると、メモリに格納されているすべての変数がマークされます。次に、環境内の変数と、環境内の変数によって参照されるトークンを削除します。

これ以降にマークされた変数は、環境内の変数からアクセスできなくなるため、削除されたと見なされます。

やっと。ガベージ コレクターはメモリのクリーンアップを完了し、マークされた値を破棄し、それらが占有していたメモリ領域を再利用します。

2. 参照カウント

参照カウントとは、各値が参照される回数を追跡することを意味します。変数が宣言され、その変数に参照型が割り当てられると、値の参照カウントは 1 になります。

逆に、この値への参照を含む変数が別の値を取得すると、この値への参照数は 1 減少します。引用数が0になると、

つまり、この値にアクセスする方法はもうないので、この値が占有しているメモリ領域を再利用することができます。こうすることで、次にガベージコレクターが実行されるときに、

参照カウントが 0 の値によって占有されているメモリを解放します。

要約する

js クロージャとガベージ コレクションのメカニズムに関するこの記事はこれで終わりです。より関連性の高い js クロージャとガベージ コレクションのコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • JavaScript の高度なクロージャの説明
  • JavaScript クロージャの説明
  • JavaScript クロージャの説明
  • Javascript のスコープとクロージャの詳細
  • JavaScript のクロージャの問題の詳細な説明
  • Javascriptのクロージャとアプリケーションの詳細な説明
  • JS の閉鎖原則とその使用シナリオの分析
  • JavaScriptクロージャの原理と機能の詳細な説明

<<:  mysqlのkey_lenの計算方法についての簡単な説明

>>:  Nginx 仮想ホスト (IP ベース) を構成する 3 つの方法の詳細な説明

推薦する

Vue のスロットリング関数使用時の落とし穴ガイド

序文一般的なビジネス シナリオでは、検索ボックスへの入力が完了した後、検索データを取得するために関連...

Viteの新しい体験の詳細な説明

Vite とは何ですか? (フロントエンドの新しいおもちゃです) Vite は、ネイティブ ES モ...

JSのバイナリファミリーについての簡単な説明

目次概要ブロブBlob の動作BLOB ダウンロード ファイルブロブ画像のローカル表示BLOB ファ...

easycomモードでUNI-APPコンポーネントを呼び出す際に習得する必要がある実践的なスキル

この記事は議論の出発点となることを目的としています。詳細なドキュメントと easycom の仕様につ...

MySQL 8.0.15 のダウンロードとインストールの詳細なチュートリアルは初心者にとって必須です。

この記事では、MySQL 8.0.15をダウンロードしてインストールするための具体的な手順を参考まで...

Dockerデータのバックアップとリカバリプロセスの詳細な説明

データのバックアップ操作は非常に簡単です。次のコマンドを実行します。 docker run --vo...

DockerでRabbitMqの共通クラスタとミラークラスタを構築する詳細な操作

目次1. RabbitMqの動作環境を構築する1.検索を通じてrabbitmqイメージを照会する2....

AngularJSループオブジェクトプロパティで動的列を実装するアイデアの詳細な説明

動的な列を実現するための Angularjs ループ オブジェクト プロパティ利点: オブジェクトを...

Vueルータールーティングの詳細な説明

目次1. 基本的な使い方2. 注意すべき点3. マルチレベルルーティング(マルチレベルルーティング)...

Alibaba Cloud Centos 7.5 に MySQL をインストールするチュートリアル

CentOS 7 の yum ソースには、MySQL を正常にインストールするための mysql-s...

HTML マークアップ言語 - リファレンス

123WORDPRESS.COM HTML チュートリアル セクションに戻るには、ここをクリックして...

Docker を使用して Spring Boot をデプロイする方法

Docker テクノロジの開発により、マイクロサービスの実装にさらに便利な環境が提供されます。Doc...

音声キューイングシステムを実装するためのJavaScript

目次導入主な特徴エフェクト表示キーコード導入音声キューイングシステムは、銀行、レストラン、病院などの...

IDEA 2020.3.1 で Tomcat をデプロイし、最初の Web プロジェクトを作成するプロセスの詳細な説明

目次Tomcat の紹介Tomcat の展開Web プロジェクトの作成tomcatの設定プロジェクト...