クロージャは、1960 年代に登場した関数型プログラミングの概念で、クロージャを実装した最初の言語は LISP の方言である Scheme でした。それ以来、クロージャ機能は他の言語で広く採用されてきました。
クロージャの厳密な定義は、「関数 (環境) とそれに囲まれた自由変数で構成されるセット」です。この定義は誰にとっても少しわかりにくいため、最初に例を通して説明し、それほど厳密ではないクロージャを説明します。次に、クロージャの古典的な使用例をいくつか示します。
クロージャとは
平たく言えば、JavaScript のすべての関数はクロージャですが、一般に、ネストされた関数は
をより適切に反映できます。
クロージャーの特性を示すために、次の例を参照してください:
var generateClosure = function() { var count = 0; var get = function() { count ++; return count; }; return get; }; var counter = generateClosure(); console.log(counter()); // 输出 1 console.log(counter()); // 输出 2 console.log(counter()); // 输出 3
このコードでは、generateClosure() 関数にローカル変数 count があり、初期値は 0 です。 get と呼ばれる関数もあります。この関数は、親スコープであるgenerateClosure() 関数内の count 変数を 1 ずつ増加させ、count の値を返します。 generateClosure() の戻り値は get 関数です。外部的には、カウンター変数を介してgenerateClosure()関数を呼び出し、その戻り値であるget関数を取得しました。その後、counter()を数回繰り返し呼び出したところ、戻り値が毎回1ずつ増加することがわかりました。
上記の例の特徴を見てみましょう。命令型プログラミングの考え方の通常の理解によれば、そのライフサイクルは、generateClosure がコール スタックから呼び出されるときの期間です。カウント変数 適用されていたスペースが解放されます。問題は、generateClosure() の呼び出しが終了した後、counter() が「すでに解放された」カウント変数を参照し、エラーが発生しないだけでなく、counter() が呼び出されるたびに count が変更されて返されることです。どうしたの?
これはまさにいわゆるクロージャの特徴です。関数がその中で定義された関数を返す場合、クロージャには返された関数だけでなく、関数が定義されている環境も含まれます。上記の例では、関数generateClosure()の内部関数getが外部変数counterから参照されている場合、counterとgenerateClosure()のローカル変数がクロージャとなります。十分に明確でない場合は、次の例が役立つかもしれません
あなたは理解しています:
var generateClosure = function() { var count = 0; var get = function() { count ++; return count; }; return get; }; var counter1 = generateClosure(); var counter2 = generateClosure(); console.log(counter1()); // 输出 1 console.log(counter2()); // 输出 1 console.log(counter1()); // 输出 2 console.log(counter1()); // 输出 3 console.log(counter2()); // 输出 2
上記の例は、クロージャがどのように生成されるかを説明しています。counter1 と counter2 はそれぞれ、generateClosure() 関数を呼び出し、クロージャの 2 つのインスタンスを生成します。内部的に参照する count 変数は、それぞれの動作環境に属します。 generateClosure() が get 関数を返すと、get が参照する可能性のあるgenerateClosure() 関数の内部変数 (つまり、count 変数) もプライベートに返され、メモリ内にコピーが生成されることがわかります。 thengenerateClosure() 返された関数の 2 つのインスタンス、counter1 と counter2 は互いに独立しています。
閉鎖の目的
1. ネストされたコールバック関数
クロージャには主に 2 つの用途があります。1 つはネストされたコールバック関数を実装すること、もう 1 つはオブジェクトの詳細を隠すことです。まず、ネストされたコールバック関数を理解するために次のコード例を見てみましょう。次のコードは、Node.js で MongoDB を使用して、ユーザーを追加する単純な関数を実装します。
exports.add_user = function(user_info, callback) { var uid = parseInt(user_info['uid']); mongodb.open(function(err, db) { if (err) {callback(err); return;} db.collection('users', function(err, collection) { if (err) {callback(err); return;} collection.ensureIndex("uid", function(err) { if (err) {callback(err); return;} collection.ensureIndex("username", function(err) { if (err) {callback(err); return;} collection.findOne({uid: uid}, function(err) { if (err) {callback(err); return;} if (doc) { callback('occupied'); } else { var user = { uid: uid, user: user_info, }; collection.insert(user, function(err) { callback(err); }); } }); }); }); }); }); };
2. プライベートメンバーの実装
JavaScript オブジェクトにはプライベート プロパティがないことがわかっています。これは、オブジェクトのすべてのプロパティが外部に公開されていることを意味します。これにより、たとえば、オブジェクトのユーザーが属性を直接変更すると、オブジェクトの内部データの一貫性が破壊されるなど、セキュリティ リスクが発生する可能性があります。 JavaScript では、このプロパティがプライベートであり、外部オブジェクトが直接読み書きできないことを示すために、すべてのプライベート プロパティ (_myPrivateProp など) の前にアンダースコアを置く規則が使用されています。しかし、これは単なる非公式の合意であり、オブジェクトのユーザーがこれを行わないと仮定すると、より厳密なメカニズムはあるのでしょうか。答えは「はい」です。クロージャを通じて実現できます。前の例をもう一度見てみましょう:
var generateClosure = function() { var count = 0; var get = function() { count ++; return count; }; return get; }; var counter = generateClosure(); console.log(counter()); // 输出 1 console.log(counter()); // 输出 2 console.log(counter()); // 输出 3
counter() を呼び出すことのみがクロージャ内の count 変数にアクセスし、ルールに従って 1 ずつインクリメントできることがわかります。他の方法で count 変数を見つけることは不可能です。この単純な例から着想を得て、オブジェクトをクロージャでカプセル化し、詳細を隠すために「アクセサ」オブジェクトのみを返すことができます。
上記がこの記事の全内容です。これが皆さんの JavaScript クロージャの学習と理解に役立つことを願っています。