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

推薦する

SVN のインストールと基本操作 (グラフィック チュートリアル)

目次1. SVNとは何か2. SVNサーバーとクライアントの取得方法3. SVN ワークフローとアー...

SNMP4J サーバー接続タイムアウト問題の解決策

弊社のネットワーク管理センターは管理センター兼サーバーとして機能します!各管理対象デバイスは、TCP...

HTML をホームページとして設定し、お気に入りに追加_Powernode Java Academy

IE ブラウザで「ホームページとして設定」および「お気に入りに追加」機能を実装する方法解決:指定さ...

Linux C バックグラウンドサービスプログラムの単一プロセス制御の実装

導入通常、バックグラウンド サーバー プログラムには 1 つのプロセスのみが必要ですが、単一のプロセ...

MySQLインストーラがコミュニティモードで実行されている場合の解決策

今日、リモートデスクトップを実行してログインしているときにこのプロンプトを見つけました「MySQL ...

JavaScriptで配列を作成する方法の詳細な説明

目次JavaScript で配列を作成する配列の使用配列を分割文字列に変換する配列に要素を追加する配...

HTMLエンコードによる文字化け問題について

今日、3年生から質問がありました。彼が書いた HTML コードを開くと、文字化けした文字が表示されま...

Docker コンテナ入門から夢中になるまで(推奨)

1. Docker とは何ですか?仮想マシンについては誰もが知っています。Windows に Li...

JavaScript配列の重複排除のいくつかの方法についての詳細な説明

目次1.重複排除を設定する2. 重複を削除するには、2 回の for ループを使用します。 3. i...

MySQL データベースの詳細な説明 (Ubuntu 14.0.4 LTS 64 ビットベース)

1. MySQLデータベースの構成と関連概念まず、MySQL はリレーショナル データベースである...

Vuexの補助関数の使い方

目次マップ状態マップゲッターマップミューテーションマップアクション複数のモジュールマップ状態 ...

WeChatミニプログラムの基本チュートリアル:Echartの使用

序文まずは最終的な効果を見てみましょう。私が自分で作った小さなデモです。まずEChartsの公式サイ...

CSS を使用して画像の色を変更する 100 の方法 (収集する価値あり)

序文「画像処理というと、PhotoShop などの画像処理ツールを思い浮かべることが多いです。フロン...

Dockerでnginxをデプロイし、設定ファイルを変更する方法

Dockerでnginxをデプロイするのはとても簡単ですたった 1 行のコマンド: docker 実...

MySQLにおけるテーブルインデックスの定義方法と導入

概要インデックスは、テーブル内の 1 つ以上の列に基づいて DBMS によって特定の順序で作成される...