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

推薦する

Nginx 1つのドメイン名で複数のプロジェクトにアクセスする方法の例

背景最近、複数のプロジェクトを展開する際に、1 つのドメイン名で複数のプロジェクトにアクセスする方法...

VUE uni-app でよく使用される API についての簡単な説明

目次1. ルーティングとページジャンプ2. インターフェース要約する1. ルーティングとページジャン...

Vue はトークンを取得してトークン ログインのサンプル コードを実装します

ログイン認証にトークンを使用する考え方は次のとおりです。 1. 初めてログインする場合、フロントエン...

関数の分類の詳細な説明とJavascriptでのこのポイントの例

JS で関数を定義する 3 つの方法例を挙げて説明しましょう。 <スクリプト> //メソ...

MySQL でデータ復旧に binlog を使用する方法

序文最近、オンラインでデータが誤って操作されました。データベースが直接変更されたため、それを回復する...

Vueのウェブページスクリーンショット機能の詳しい説明

最近、プロジェクトで写真をアップロードする要件があるのですが、顧客がアップロードする写真のサイズがま...

指定された期間内のすべての日付または月を取得する MySQL ステートメント (ストアド プロシージャの設定やテーブルの追加は不要)

mysql は期間内のすべての日付または月を取得します1: mysqlは期間内のすべての月を取得し...

数十億のデータに対するMySQLページングの最適化に関する簡単な説明

目次背景分析するデータシミュレーション1. 従業員テーブルと部門テーブルの2つのテーブルを作成します...

CentOS 8 に htop をインストールする方法のチュートリアル

システムをインタラクティブに監視したい場合は、htop コマンドが最適な選択肢の 1 つです。 ht...

jQueryはアコーディオンの小さなケースを実装します

この記事では、アコーディオンを実装するためのjQueryの具体的なコードを参考までに紹介します。具体...

CSS フレックスベースのテキストオーバーフロー問題の解決方法

重要でないflex-basisテキストオーバーフローに省略記号を追加するという小さな機能に多くの問題...

非常に詳細な基本的なJavaScript構文ルール

目次01 JavaScript(略称:js) js は 3 つの部分に分かれています。 JavaSc...

ウェブタイポグラフィにおける致命的な意味的ミス 10 選

<br />これは、Steven D が書いた Web フロントエンド開発デザインの基本...

mysql 結合クエリ (左結合、右結合、内部結合)

1. MySQLの一般的な接続INNER JOIN (内部結合、または等価結合): 2 つのテーブ...

複数の .sql ファイルを MySQL に効率的にインポートする方法の詳細な説明

MySQL には、複数の .sql ファイル (SQL ステートメントを含む) をインポートする方法...