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 つの方法の詳細な説明

推薦する

JDBC を使用して Mysql データベースに接続する際に発生する可能性のある問題の概要

まず、いくつかの概念を明確にします。 JDBC: Javaデータベース接続、Oricalによって規定...

CSS設定div背景画像実装コード

コンポーネントに背景画像コントロールを追加するには、次の 2 つの手順だけが必要です。 <表示...

高い同時実行性の下でNginxのパフォーマンスを最適化する方法をまとめます

目次特徴利点インストールとコマンド設定ファイルプロキシモードとリバースプロキシ構成フォワードプロキシ...

レスポンシブ Web デザイン手法を実装し、ウォーターフォール モデルに別れを告げる 5 つのステップ (グラフィック チュートリアル)

次の Web デザイン プロジェクトはレスポンシブにする必要があると上司をようやく納得させることがで...

Vue ルーター vue-router 詳細説明ガイド

中国語ドキュメント: https://router.vuejs.org/zh/ Vue Router...

Nest.js パラメータ検証とカスタム戻りデータ形式の詳細な説明

0x0 パラメータ検証Nest.jsでは、パラメータ検証業務のほとんどをパイプライン方式で実装してい...

「fsck」を使用して Linux のファイルシステムエラーを修正する方法

序文ファイル システムは、データの保存方法と復元方法を整理する役割を担います。 いずれにせよ、時間の...

CSS カウンターを使用して数字の順序付きリストを美しく表示する方法

Web デザインでは、Web サイトに表示されるデータの構造とコンテンツをユーザーが明確に理解できる...

VMware 構成 VMnet8 ネットワーク方法の手順

目次1. はじめに2. 設定手順1. はじめに1. NAT モード (VMnet8) は、仮想マシン...

HTML テーブル マークアップ チュートリアル (2): テーブル境界属性 BORDER

デフォルトでは、テーブルの境界線は 0 ですが、テーブルの境界線を設定できます。基本的な構文<...

MySQL 8.0.13 zipパッケージのインストール方法について

MySQL 8.0.13 にはデフォルトでデータ フォルダがあります。このフォルダを削除する必要があ...

CentOS 7へのJenkinsのインストール手順の詳細な説明

Yum経由でJenkinsをインストールする1. インストール # yum ソースをインポート wg...

HTML におけるベースタグの使用に関する詳細な説明

requireJS には、baseURL というプロパティがあります。baseURL を設定すること...

簡単な約束を段階的に実行する方法を教えます

目次ステップ1: フレームワークを構築するステップ2 構築されたPromiseフレームワークに入力す...

Vue3 のレンダリング関数における互換性のない変更の詳細な説明

目次レンダリングAPIの変更レンダリング関数のパラメータレンダリング関数のシグネチャの変更VNode...