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

推薦する

テキストエリアの disabled 属性と readonly 属性の具体的な使用法

障害者の定義と使用法disabled 属性はブール属性です。 disabled 属性は、テキスト領域...

RedHat 6.5/CentOS 6.5 に MySQL 5.7.20 をインストールするための詳細なチュートリアル

rpmインストールパッケージをダウンロードするMySQL公式サイト: https://dev.mys...

Vue で ToDo アプリケーションを実装する例

背景まず最初に、私はフロントエンド開発の専門家ではないことを述べておきたいと思います。私の以前のコン...

クリエイティブな会社概要ウェブページデザイン

ユニークな「About」ページ自分を他の人たちと差別化する素晴らしい方法は、本当にユニークな自己紹介...

.html、.htm、.shtml、.shtm の違いと関連性について簡単に説明します。

ご存知のとおり、私たちが毎日閲覧する Web ページ、Web サイト、または Web ページには独自...

MySQLテーブル名の大文字と小文字を区別しない設定方法の詳細な説明

デフォルトでは、Linux の MySQL はテーブル名の大文字と小文字を区別します。 MySQL ...

HTML レイヤード ボックス シャドウ効果のサンプル コード

まず、画像を見てみましょう。今日はこのエフェクトを作成します。 実は、何でもないんです。Web ペー...

TypeScript におけるジェネリックケースの詳細な説明

ジェネリックの定義 // 要件 1: ジェネリックは指定されていないデータ型をサポートできるため、渡...

標準SQL更新ステートメントの3つの用途についての簡単な理解

1. 環境: MySQL-5.0.41-win32 Windows XP プロフェッショナル2. テ...

MySQLトランザクションとSpring分離レベルの実装原理の詳細な説明

1. トランザクションはACID特性を持つ原子性: トランザクションは、トランザクションによって分割...

CSSの固定位置属性の詳細な説明

モバイル アプリを開発する場合、Web サイトが特定の高さまでスクロールしたときにコンテンツの一部を...

ユーザーはその理由を知る必要がある

証券会社にいた頃、設計業務が忙しくなかったため、商品のマニュアルを書く役割を担ったことがありました。...

MySQLでテーブルを接続するいくつかの方法

MySQL テーブルでの接続方法は実は非常に簡単なので、ここではその特徴を簡単にリストします。テーブ...

Vue はユーザーのログイン状態を維持します (さまざまなトークン保存方法)

目次クッキーの設定方法クッキーのデメリット: LocalStorage と SessionStora...

Docker に MySQL をデプロイする例

目次1 コンテナクラウドとは何ですか? 2 Dockerの紹介3 dockerを使ってMySQLをイ...