JavaScript のクロージャの詳細な説明

JavaScript のクロージャの詳細な説明

導入

クロージャは JavaScript の非常に強力な機能です。いわゆるクロージャは関数内の関数です。内部関数は外部関数のスコープにアクセスできるため、クロージャを使用してより強力な作業を行うことができます。

今日はクロージャについて詳しく紹介します。

関数内の関数

関数内の関数は親関数のスコープ内の変数にアクセスできることを説明しました。例を見てみましょう。

関数親関数() {
 var アドレス = 'flydean.com'; 
 関数アラートアドレス() { 
 アラート(アドレス); 
 }
 アラートアドレス();
}
親関数();

上記の例では、parentFunction に変数 address を定義し、parentFunction 内に alertAddress メソッドを定義し、このメソッド内で外部関数に定義された address 変数にアクセスします。

上記のコードを実行しても問題はなく、データに正しくアクセスできます。

閉鎖

関数内に関数が存在するようになりましたが、クロージャとは何でしょうか?

次の例を見てみましょう。

関数親関数() {
 var アドレス = 'flydean.com'; 
 関数アラートアドレス() { 
 アラート(アドレス); 
 }
 alertAddress を返します。
}
var myFunc = 親関数();
myFunc();

この例は最初の例と非常に似ていますが、内部関数を返してそれを myFunc に割り当てる点が異なります。

次に、myFunc を直接呼び出しました。

parentFunction はすでに実行を完了して返されているにもかかわらず、MyFunc は parentFunction 内のアドレス変数にアクセスします。

しかし、myFunc を呼び出すと、アドレス変数にアクセスできます。これで終わりです。

クロージャのこの機能は非常に便利です。以下に示すように、クロージャを使用して関数ファクトリを生成できます。

関数makeAdder(x) {
 関数(y)を返す{
 x + y を返します。
 };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

コンソール.log(add5(2)); // 7
コンソール.log(add10(2)); // 12

Add5 と add10 はどちらもクロージャであり、makeAdder 関数ファクトリによって作成されます。異なる x パラメータを渡すことで、add メソッドの異なるベースが得られます。

最後に、2 つの異なる追加メソッドが生成されます。

関数ファクトリの概念を使用して、クロージャの実際のアプリケーションを検討することができます。たとえば、ページには 3 つのボタンがあり、これらのボタンをクリックするとフォントを変更できます。

まず関数ファクトリを通じて 3 つのメソッドを生成できます。

関数makeSizer(サイズ) {
 関数()を返す{
 document.body.style.fontSize = サイズ + 'px';
 };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

これら 3 つのメソッドを使用して、DOM 要素をコールバック メソッドにバインドします。

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

クロージャを使用してプライベートメソッドを実装する

Java と比較すると、Java にはプライベート アクセス記述子があります。プライベートを使用すると、メソッドがクラス内でのみアクセス可能であることを指定できます。

もちろん、JS にはそのようなものはありませんが、クロージャを使用して同じ効果を得ることができます。

var カウンター = (関数() {
 var プライベートカウンタ = 0;
 関数changeBy(val) {
 プライベートカウンタ += val;
 }

 戻る {
 増分: 関数() {
  変更(1)
 },

 減算: 関数() {
  変更(-1);
 },

 値: 関数() {
  privateCounter を返します。
 }
 };
})();

console.log(counter.value()); // 0.

カウンタを増分します。
カウンタを増分します。
console.log(counter.value()); // 2.

カウンタを減分します。
console.log(counter.value()); // 1.

親関数で privateCounter プロパティと changeBy メソッドを定義しましたが、これらのメソッドにアクセスできるのは内部関数内のみです。

クロージャの概念を使用してこれらのプロパティとメソッドをカプセル化し、外部で使用できるように公開することで、最終的にプライベート変数とメソッドのカプセル化の効果を実現します。

スコープ チェーン オブ クロージャ

各クロージャには、関数自体のスコープ、親関数のスコープ、およびグローバル スコープを含むスコープが存在します。

関数内に新しい関数を埋め込むと、スコープ チェーンが形成されます。これをスコープ チェーンと呼びます。

以下の例をご覧ください。

// グローバルスコープ
var e = 10;
関数 sum(a){
 関数(b)を返す{
 関数(c){を返す
  // 外部関数スコープ
  関数(d)を返す{
  // ローカルスコープ
  a + b + c + d + e を返します。
  }
 }
 }
}

console.log(sum(1)(2)(3)(4)); // ログ20

クロージャに関する一般的な問題

最初の一般的な問題は、ループのトラバーサルでクロージャを使用することです。例を見てみましょう。

関数 showHelp(ヘルプ) {
 document.getElementById('help').innerHTML = ヘルプ;
}

関数setupHelp() {
 var ヘルプテキスト = [
  {'id': 'email', 'help': 'あなたのメールアドレス'},
  {'id': 'name', 'help': 'あなたのフルネーム'},
  {'id': 'age', 'help': 'あなたの年齢(16歳以上である必要があります)'}
 ];

 (var i = 0; i < helpText.length; i++) {
 var item = helpText[i];
 document.getElementById(item.id).onfocus = 関数() {
  アイテムのヘルプを表示します。
 }
 }
}

セットアップヘルプ();

上記の例では、setupHelp 関数を作成しました。setupHelp では、onfocus メソッドにクロージャが割り当てられているため、クロージャ内の項目は外部関数で定義された項目変数にアクセスできます。

ループ内で値を割り当てるため、実際には 3 つのクロージャが作成されますが、これらの 3 つのクロージャは外側の関数の同じスコープを共有します。

私たちの意図は、異なる ID によって異なるヘルプ メッセージがトリガーされることです。しかし、実際に実行すると、どの ID であっても、最終メッセージは最後のものであることがわかります。

onfocus はクロージャが作成された後にのみトリガーされるため、item の値は実際にこの時点で変更されます。ループが終了すると、item の値は最後の要素を指しているため、表示されるのは最後のデータのヘルプ メッセージだけです。

この問題を解決するにはどうすればいいでしょうか?

最も簡単な方法は、ES6 で導入された let 記述子を使用して、項目をブロックのスコープとして定義することです。ループが実行されるたびに新しい項目が作成されるため、クロージャ内の項目の値は変更されません。

 (i = 0 とします; i < helpText.length; i++) {
 アイテムをhelpText[i]とします。
 document.getElementById(item.id).onfocus = 関数() {
  アイテムのヘルプを表示します。
 }
 }

別の方法は、別のクロージャを作成することです。

関数makeHelpCallback(help) {
 関数()を返す{
 showHelp(ヘルプ);
 };
}

 (var i = 0; i < helpText.length; i++) {
 var item = helpText[i];
 document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
 }

ここでは、前述の関数ファクトリーの概念が使用され、異なるクロージャに対して異なるスコープ環境が作成されます。

もう 1 つの方法は、アイテムを新しい関数スコープに含めて、各作成が新しいアイテムになるようにすることです。これは、let の原則に似ています。

 (var i = 0; i < helpText.length; i++) {
  (関数() {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = 関数() {
     アイテムのヘルプを表示します。
    }
  })(); 
 }

2 番目によくある問題はメモリ リークです。

 関数 parentFunction(paramA)
 {
 var a = パラメータ;
 関数childFunction()
 {
 a + 2 を返します。
 }
 子関数() を返します。
 }

上記の例では、childFunction は parentFunction の変数 a を参照します。 childFunction がまだ使用されている限り、は解放できず、parentFunction をガベージ コレクションできなくなります。

クロージャのパフォーマンスの問題

オブジェクトを定義し、クロージャを通じてそのプライベート プロパティにアクセスします。

関数 MyObject(名前, メッセージ) {
 this.name = name.toString();
 this.message = message.toString();
 this.getName = 関数() {
  this.name を返します。
 };

 this.getMessage = 関数() {
  this.message を返します。
 };
}

上記のオブジェクトの何が問題なのでしょうか?

上記のオブジェクトの問題は、新しいオブジェクトごとに getName メソッドと getMessage メソッドがコピーされ、一方ではコンテンツの冗長性が生じ、他方ではパフォーマンスに影響することです。

一般的に言えば、プロトタイプ上でオブジェクトのメソッドを定義します。

関数 MyObject(名前, メッセージ) {
 this.name = name.toString();
 this.message = message.toString();
}
MyObject.prototype.getName = 関数() {
 this.name を返します。
};
MyObject.prototype.getMessage = 関数() {
 this.message を返します。
};

プロトタイプ全体を直接書き直すと、不明なエラーが発生するので注意してください。必要に応じて特定のメソッドのみを書き換える必要があります。

要約する

クロージャは JS において非常に強力で便利な概念です。気に入っていただければ幸いです。

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

以下もご興味があるかもしれません:
  • Javascriptのクロージャを学ぶ
  • JavaScript 知識ポイントのまとめ (XVI) Javascript クロージャコードの詳細な説明
  • JavaScript 関数型プログラミングにおけるクロージャの理解
  • JavaScript のクロージャの詳細な説明
  • JavaScript クロージャの使用例の簡単な分析
  • Javascript クロージャ
  • JavaScript クロージャの詳細

<<:  MySQLのインストールと設定方法のグラフィックチュートリアル(CentOS7)

>>:  MySQL 5.7 インストール MySQL サービスを開始できませんが、サービスはエラーを報告しません

推薦する

CSS シャドウ効果の比較: ドロップシャドウとボックスシャドウ

Drop-shadow と box-shadow はどちらも影効果(ハロー効果)のための CSS プ...

MySQL における INSERT INTO SET の利点

MySQL データベースにデータを挿入します。以前はよく使われていた INSERT INTO テーブ...

MySQL データ圧縮パフォーマンス比較の詳細

目次1. テスト環境1.1 ハードウェアとソフトウェア1.2 テーブル構造2. テストの目的2.1 ...

Vue プロジェクトで mock.js を使用するための完全な手順

Vue プロジェクトで mock.js を使用する開発ツールの選択: Vscode 1. コマンドラ...

CSS を使用して HTML フォーム コントロールを美しくする詳細な例 (フォームの美化)

1. HTML送信ボタンと下部ボタンの基本構文構造1. HTML送信ボタン入力タグで type=&...

MySQL 無料インストール版 (zip) のインストールと設定の詳細なチュートリアル

この記事では、MySQL無料インストール版(zip)のインストールと設定のチュートリアルを参考までに...

Vue Notepadの例の詳細な説明

この記事の例では、メモ帳機能を実装するためのVueの具体的なコードを参考までに共有しています。具体的...

Docker に Tomcat をインストールし、Springboot プロジェクトの WAR パッケージをデプロイする方法

簡単です。チュートリアルを見てください。ブロガー1. まずdockerを起動するサービスdocker...

MySQL テーブルパーティションの使用法と基本原理の詳細な説明

目次パーティションテーブルとはパーティションテーブルの適用シナリオパーティションテーブルの制限パーテ...

Vue3ナビゲーションバーコンポーネントのカプセル化実装方法

参考までに、Vue3でナビゲーションバーコンポーネントをカプセル化し、スクロールバーのスクロールに合...

MySQL の NULL と空の文字列

最近、MySQL に触れました。昨日、テーブル構造情報を格納するための新しいテーブルを作成しました。...

MySQL SQL文を最適化するためのヒント

十分に最適化されていない、またはパフォーマンスが極端に低い SQL ステートメントに直面した場合、通...

CentOS のクローン作成、Linux 仮想マシンの共有の完全な手順

序文Linux が完全にセットアップされると、クローン機能を使用して短時間で複数の Linux を作...

単一選択折りたたみメニュー機能を実現するCSS

前回の「最もシンプルなスイッチを実現するCSS」のように、HTML5とCSS3でほとんどの機能をすで...

MySQL がテーブルを読み取れないエラー (MySQL 1018 エラー) の解決方法

1. エラーの再現MySQL データベースにはアクセスできますが、データベース テーブルを読み取るこ...