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 サービスを開始できませんが、サービスはエラーを報告しません

推薦する

Docker-compose インストール yml ファイルの設定方法

目次1. オフラインインストール2. オンラインインストール3. アンインストール4. ymlファイ...

Ubuntu 20.04にvncserverをインストールする方法

Ubuntu 20.04は2020年4月に正式にリリースされました。本日、ミラーシステムを正式にイン...

Ubuntu 18.04 で中国語入力方法を設定する方法

Ubuntuの最新バージョンでは、ユーザーは中国語入力方法を別途ダウンロードする必要がなくなりました...

私が遭遇したIE8の互換性に関する注意事項

1. IE8 の getElementById は id のみをサポートし、name はサポートしま...

MySQL における冗長インデックスと重複インデックスの違い

MySQL では、1 つの列に複数のインデックスを作成できます。意図的であるかどうかにかかわらず、M...

vue で h5 側のアプリを開きます (Android か Apple かを判断します)

1. 開発環境 vue+vant 2. コンピュータシステム Windows 10 Profess...

Vue プロジェクトで axios リクエストを使用する方法

目次1. インストール2. カプセル化に問題はない3. ファイルを作成する4. アドレス設定をリクエ...

純粋なCSSを使用してスクロールシャドウ効果を実現します

端的に言うと、スクロール可能な要素には非常によくある状況があります。通常、スクロールすると、要素が現...

純粋な CSS を使用して 3D 回転効果を実装するサンプル コード

3D効果を実現するには、主にCSSのpreserve-3dプロパティとperspectiveプロパテ...

jQueryは、マウスをドラッグしてdivの位置とサイズを変更する方法を実装しています。

Windows フォームと同様の効果を得るには、中央をドラッグして div の位置を変更し、端をド...

MySQL 8.0.12 の詳細なインストールおよびアンインストール チュートリアル

1. MySQL 8.0.12 バージョンのインストール手順。 1. ダウンロードhttps://d...

CentOS で MySQL を完全にアンインストールする方法

この記事では、CentOSでのMySQLの完全アンインストールについて記録しています。具体的な内容は...

Windows でのシンプルな Mysql バックアップ BAT スクリプトの共有

序文この記事では、Windows で Mysql をバックアップするための簡単な BAT スクリプト...

Docker Compose の実践とまとめ

Docker Compose は、Docker コンテナ クラスターのオーケストレーションを実現しま...

Mysql で自動増分主キー ID を更新するときに問題が発生しました

目次自動インクリメント ID を更新する理由は何ですか?質問解決方法これは私が知っている問題ですが、...