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

推薦する

MySQL グローバルロックとテーブルレベルロックの具体的な使用法

目次序文グローバルロックテーブルロックテーブルロックメタデータ ロック (MDL ロック)要約する参...

クエリプロファイラを使用して MySQL ステートメントの実行時間を表示する方法

前回の記事では、MySQL ステートメントの実行時間をチェックする 2 つの方法を紹介しました。今日...

Linux でスレッドを作成するための pthread_create の具体的な使用法

pthread_create関数機能紹介pthread_createはUNIX環境のスレッド作成関数...

Mysql テーブルで利用可能な最小 ID 値を照会する方法

今日、研究室のプロジェクトを見ていたとき、私にとって「難しい」問題に遭遇しました。実は、それは私があ...

MySQL の count()、group by、order by の詳細な説明

最近、IM を実行するときに、これらの 3 つのキーワードを同時に使用したときに問題が発生しました。...

MySQLに画像を保存する方法

1 はじめにデータベースを設計する場合、画像や音声ファイルをデータベースに挿入することは避けられませ...

アクセス速度を上げるためにウェブサイトを最適化する方法の更新

最近、同社はitpubを皮切りに、コーポレートウェブサイト傘下の全サイトの評価を開始した。そのために...

Linux アカウントのパスワードを変更する詳細な例

個人アカウントのパスワードを変更する一般ユーザーが個人アカウントのパスワードを変更する場合は、他のコ...

VMware 上の CentOS に Oracle12.2 をサイレント インストールする詳細なグラフィック チュートリアル

環境準備: VMware+CentOS、jdk 1. システムディスクのサイズを確認する1. コマン...

CSS3 フィルターを使用して PNG 画像の色を変更するサンプル コード

この方法は、CSS3のdrop-shadow filterを使用して、png画像の不透明部分に任意の...

Centos7 MySQL データベースのインストールと設定のチュートリアル

1. システム環境yum updateアップグレード後のシステムバージョンは[root@yl-web...

Jenkins + Docker + ASP.NET Core の自動デプロイメントの問題について (落とし穴を避ける)

このブログを書くつもりはなかったのですが、実際の操作中に、ネットワークの問題に圧倒されたこと (ネッ...

Linux QT Kit が見つからない、バージョンが空の問題の解決策

現在このような問題が発生しています 私の状況は、QT が動かなくなってしまったため、仮想マシンを再起...

MySQL でタイムスタンプを日付に変換する例

序文職場で次のような状況に遭遇しました。ログ システムのテーブルでは、時間フィールドには日付データで...

CentOS 7 で Docker のポート転送をファイアウォールと互換性のあるように設定する方法

CentOS 7 では、次のようなコマンドを使用してホスト ポートをコンテナー ポートにマッピングす...