JavaScriptクロージャの原理と機能の詳細な説明

JavaScriptクロージャの原理と機能の詳細な説明

導入

例示する

この記事では、JavaScript クロージャの役割、目的、原則について説明します。

閉鎖の定義

クロージャとは、内部関数が外部関数内でも常に外部関数で宣言された変数とパラメータにアクセスできることを意味します。

番号が返却された後(有効期限が切れます)。

クロージャの役割(特徴)

1. 関数内の関数

2. 内部関数は外部関数のパラメータや変数を参照できる

3. 外部関数のパラメータと変数は内部関数によって参照されるため、ガベージコレクションの対象になりません。

クロージャとグローバル変数

クロージャの使用

カレー作り

パラメータを通じてさまざまな関数を生成できます。

関数makeWelcome(x) {
	関数(y)を返す{
		x + y を返します。
	};
}
 
sayHello = makeWelcome("Hello,") とします。
sayHi = makeWelcome("Hi,") とします。
 
console.log(sayHello("Tony"));
console.log(sayHi("Tony"));

結果

こんにちは、トニー

こんにちは、トニー

パブリック変数の実装

要件: 呼び出されるたびに 1 回増加するアキュムレータを実装します。

関数makeCounter(){
	count = 0 とします。
	関数 innerFunction(){
		count++ を返します。
	}
	内部関数を返します。
}
カウンターを makeCounter() にします。
 
コンソールにログ出力します。
コンソールにログ出力します。
コンソールにログ出力します。

結果

0

1

2

キャッシュ

処理に非常に時間のかかる関数オブジェクトがあるとします。計算された値は保存できます。この関数が呼び出されると、まずキャッシュ内で検索されます。見つからない場合は計算が行われ、キャッシュが更新されて値が返されます。見つかった場合は、見つかった値が直接返されます。

クロージャは外部参照を解放しないため、関数内の値を保持できるため、これを実行できます。

簡潔にするために、この記事では読み取り/書き込みキャッシュの例を直接記述します。 (読み取れない場合は計算してキャッシュに保存する代わりに)。

キャッシュ = 関数 () {
	// マップでは任意のタイプのキーを使用できます。 let storage = {} と記述した場合、キーには文字列のみを指定できます。let storage = new Map();
	戻る {
		setCache: 関数(k, v) {
			ストレージ[k] = v;
		},
		getCache: 関数 (k) {
			ストレージ[k]を返します。
		},
		キャッシュを削除する: 関数 (k) {
			ストレージ[k]を削除します。
		}
	}
}();
 
キャッシュをsetCache('a', 1);
コンソールログ(キャッシュの取得キャッシュ('a'))

結果

1

カプセル化(属性のプライベート化)

内部変数には、提供されたクロージャを通じてのみアクセスできます。 (この方法は良くないので、プロトタイプチェーンを使用することをお勧めします)。

person = function(){ とする
	// 変数のスコープは関数内にあり、外部からアクセスすることはできません。let name = "defaultName";
 
	戻る {
		getName: 関数(){
			名前を返します。
		},
		setName: 関数(newName){
			名前 = 新しい名前;
		}
	}
}();
 
console.log(人名);
コンソールにログ出力します。
person.setName("こんにちは");
コンソールにログ出力します。

結果

未定義

デフォルト名

こんにちは

閉鎖の原則

カウンターを例に挙げてみましょう:

関数makeCounter() {
	count = 0 とします。
	関数()を返す{
		count++ を返します。
	};
}
カウンターを makeCounter() にします。
コンソールにログ出力します。
コンソールにログ出力します。
コンソールにログ出力します。

結果

0

1

2

各 makeCounter() 呼び出しの開始時に、その makeCounter ランタイムの変数を格納するための新しい LexicalEnvironment オブジェクトが作成されます。

したがって、ネストされた語彙環境には 2 つのレベルがあります。

makeCounter() を実行すると、1 行だけを占めるネストされた関数 return count++ が作成されます。まだ実行していません。作成しただけです。

すべての関数は、それが「誕生」したときに作成された語彙環境を記憶します。理由: すべての関数には、関数が作成された語彙環境への参照を保持する [[Environment]] と呼ばれる隠し属性があります。

したがって、counter.[[Environment]] には {count: 0} 字句環境への参照があります。これは、関数が呼び出される場所に関係なく、関数が作成された場所を記憶する方法です。 [[Environment]] 参照は関数の作成時に設定され、永続的に保存されます。

その後、counter() が呼び出されると、その呼び出しに対して新しい Lexical Environment が作成され、その外部 Lexical Environment 参照が counter.[[Environment]] から取得されます

ここで、counter() 内のコードが count 変数を探すとき、最初に自身の Lexical Environment (ローカル変数がないため空) を検索し、次に外側の makeCounter() の Lexical Environment を検索して、見つかった場所に count 変数を固定します。

変更します (変数が存在する語彙環境内の変数を更新します)。

実行後のステータスは次のとおりです。

counter() を複数回呼び出すと、count 変数は同じ位置で 2、3 などに増加します。

ガベージコレクション

導入

通常、関数呼び出しが完了すると、レキシカル環境とその中のすべての変数は、参照がなくなるためメモリから削除されます。

JavaScript の他のオブジェクトと同様に、語彙環境はアクセス可能である限りメモリ内に保持されます。ただし、関数が終了した後もまだ到達可能なネストされた関数がある場合、その関数には、Lexical Environment を参照する [[Environment]] 属性があります。

関数が完了した後も、レキシカル環境がまだ到達可能である場合、ネストされた関数は有効なままになります。例えば:

関数f(){
    値を 123 とします。
    関数()を返す{
        アラート(値);
    }
}
// g.[[Environment]] は対応する f() 呼び出しの語彙環境への参照を格納します。let g = f();

f() が複数回呼び出され、返された関数が保存されると、対応するすべての LexicalEnvironment オブジェクトもメモリ内に保持されます。例えば:

関数f(){
    値を Math.random() とします。
    関数を返す(){
        アラート(値);
    };
}
 
// 配列内の 3 つの関数。それぞれが対応する f() の語彙環境に関連付けられています。let arr = [f(), f(), f()];

LexicalEnvironment オブジェクトが到達不能になると、他のオブジェクトと同様に終了します。つまり、少なくとも 1 つのネストされた関数がそれを参照している限り存在します。

次のコードでは、ネストされた関数が削除されると、それを囲むレキシカル環境 (およびその中の値) もメモリから削除されます。

関数f(){
    値を 123 とします。
    関数()を返す{
        アラート(値);
    }
} 
let g = f(); // g関数が存在する間、値はメモリに保持されます g = null; // これでメモリがクリーンアップされます

実際の開発における最適化

これまで見てきたように、理論上は関数が到達可能である場合、その外側にあるすべての変数も存在することになります。しかし実際には、JavaScript エンジンはそれを最適化しようとします。変数の使用状況を分析し、未使用の外部変数があることがコードから明らかな場合は、それらが削除されます。

V8 (Chrome、Opera) での重要な副作用は、そのような変数がデバッグで使用できなくなることです。

Chrome ブラウザの開発者ツールを開き、次のコードを実行してみてください。

    関数f(){
        値を Math.random() とします。
        関数g(){
            デバッガ;
        }
        g を返します。
    } 
    g = f() とします。
    g();

コードが「debugger;」の場所まで実行されると一時停止します。このとき、コンソールに console.log(value); と入力します。

結果: エラー: VM146:1 キャッチされない参照エラー: 値が定義されていません

これにより、興味深いデバッグの問題が発生する可能性があります。たとえば、予想される変数の代わりに、同じ名前の外部変数が表示されることがあります。

let value = "サプライズ!";
関数f(){
    value = "最も近い値";
    関数g(){
        デバッガ;
    }
    g を返します。
}
g = f() とします。
g();

コードが「debugger;」の場所まで実行されると一時停止します。このとき、コンソールに console.log(value); と入力します。

結果: 出力: 驚き。

以上がJavaScriptクロージャの原理と機能の詳細な内容です。JavaScriptクロージャの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • JSにおけるクロージャの役割について詳しく話しましょう
  • JavaScript の高度なクロージャの説明
  • JavaScript クロージャの詳細
  • JavaScript クロージャの説明
  • JavaScriptのクロージャとは何かを学びましょう

<<:  tomcat ログ ディレクトリ内のログ ファイルの分析 (概要)

>>:  MySQLの整数データ型tinyintの詳細な説明

推薦する

Docker ベースの Selenium 分散環境の構築

1.画像をダウンロードするdocker pull selenium/hub docker pull ...

Alibaba Cloud centos7にmysql8.0.22をインストールする詳細なチュートリアル

1. MySQLインストールパッケージをダウンロードするまず、https://dev.mysql.c...

行の高さと垂直方向の配置に関する包括的な理解

前の単語line-height、font-size、vertical-align は、インライン要素...

VueのID認証管理とテナント管理の詳細な説明

目次概要ボタンレベルの権限アイデンティティ認証管理R/U 権限権限の更新テナント管理テナント切り替え...

Vue+Elementバックグラウンド管理フレームワークの統合実践

目次Vue+ElementUI バックグラウンド管理フレームワークでは、ElementUI とは何で...

CentOS に MySQL 5.5 をインストールするための完全な手順

目次1. インストール前の準備、インストールパッケージのダウンロード1 インストールの準備2 インス...

Jenkins は Docker イメージを構築し、Harbor ウェアハウスにプッシュします

目次DockerファイルドキュメントJenkins の設定Spring Boot プロジェクトでは、...

SpringBoot プロジェクトの Docker 環境を実行するときに発生する無限再起動問題の詳細な説明

もしかしたら私の考え方が間違っていたのかもしれないし、問題の説明が少し乱雑だったのかもしれないが、こ...

Linux での Python スクリプトの自動起動とスケジュール起動の詳細な手順

1. Pythonは起動時に自動的に実行されますPython の自己起動スクリプトがauto.pyで...

Springboot プロジェクトの Docker-compose イメージリリースプロセス分析

導入Docker-Compose プロジェクトは、Docker コンテナ クラスターの迅速なオーケス...

Kali Linux インストール VMware ツールのインストール プロセスと VM インストール vmtools ボタン グレー

Xiaobai は vmtools のインストールを記録します。 1. 意義と機能: VMWARE ...

Centos7.3 で mysql5.7.18 をインストールして初期パスワードを変更する方法

この記事では、Centos7.3でのmysql5.7.18のインストールと初期パスワードの変更につい...

VUE無限レベルツリーデータ構造表示の実装

目次コンポーネントの再帰呼び出しレンダリングメソッドの使用プロジェクトに取り組んでいると、左側のメニ...

Linux Crontab シェル スクリプトを使用して第 2 レベルのスケジュールされたタスクを実装する方法

1. シェルスクリプトcrontab.shを書く #!/bin/bash step=1 #ステップ間...